#!/usr/bin/env python # # This script converts a textual (YAML) description of an ELF file to # an equivalent 'binary' file. # # The YAML description may have the following top-level keys: # # 'elf_fillchar': char # Sets the fill character to 'char'. # 'ehdr': EHDR-DESCRIPTOR # Defines an ELF Ehdr structure. # 'phdrtab': list-of(PHDR-DESCRIPTOR) # Defines the contents of the ELF Program Header table. # Each `Phdr' descriptor represents one ELF Phdr entry. # 'sections': list-of(SECTION-DESCRIPTOR) # Defines the content of each section in the file. Each # `SECTION-DESCRIPTOR' contains information for the # section `header' and the actual data for the section. # # The script will compute reasonable defaults for any fields # left unspecified in the YAML description. # # Descriptors EHDR-DESCRIPTOR and PHDR-DESCRIPTOR may be specified # as a YAML key-value set. The key names correspond to the # field names of the corresponding ELF structures, e.g., 'e_machine' # and 'e_ident' for the Ehdr and 'p_type' and 'p_paddr' for # a Phdr entry. # # Descriptor SECTION-DESCRIPTOR contains the fields in an ELF # Shdr structure and an additional member 'sh_data', whose # value is the data for the section. # # Example: # # # ehdr: !Ehdr # e_ident: !Ident # ei_class: ELFCLASS32 # ei_data: ELFDATA2MSB # e_machine: EM_PPC # phdrtab: # - !Phdr # ph_type: PHT_NULL # ... other program header fields ... # - !Phdr # ... etc. ... # sections: # - !Section # sh_name: .dynsym # ... other section header fields ... # sh_data: # ... list of data ... # - !Dyn # d_tag: 0xdeadcode # - !Dyn # d_tag: 0xcafebabe # - !Section # sh_name: .shstrtab # sh_type: SHT_STRTAB # sh_data: # - string1 # - string2 # # # :: Handling of strings :: # # Fields like 'sh_name' (in a section header) are defined to contain # an integer index into a specified string table (in this case a # section with name '.shstrtab'). Other ELF data structures use a # similar convention; names in a '.dynamic' section as stored as # indices into a '.dynstr' section. In the YAML descriptor, such # fields may be specified as indices, which are used as-is, or as text # strings which are converted to the appropriate string index. # For convenience in creating ELF objects with a large number of # sections, a section index may be manually specified using a # 'sh_index' pseudo field. # # $Id: elfc 1718 2011-08-12 07:30:43Z jkoshy $ version = "%prog 1.0" usage = "usage: %prog [options] [input-file]" description = """Create an ELF binary from a textual description in """ + \ """'input-file' (or stdin)""" import optparse, re, struct, sys, types, yaml class ElfError(Exception): """An exception signalled during conversion.""" def __init__(self, node=None, msg=None): """Initialize an exception object. Arguments: node -- a YAML parse tree node. msg -- human readable message associated with this exception. """ if node: self.ee_start = node.start_mark.line + 1 self.ee_end = node.end_mark.line + 1 else: self.ee_start = self.ee_end = -1 self.ee_msg = msg def __str__(self): """Form a printable representation of an exception.""" if self.ee_start != -1: if self.ee_start == self.ee_end: return "Error: line %d: %s" % (self.ee_start, self.ee_msg) else: return "Error: lines %d--%d: %s" % \ (self.ee_start, self.ee_end, self.ee_msg) else: return "Error: %s" % self.ee_msg # # Mappings used by the 'encode()' function # elf_cap_tag = { 'CA_SUNW_NULL': 0, 'CA_SUNW_HW_1': 1, 'CA_SUNW_SF_1': 2 } elf_d_flags = { 'DF_ORIGIN': 0x0001, 'DF_SYMBOLIC': 0x0002, 'DF_TEXTREL': 0x0004, 'DF_BIND_NOW': 0x0006, 'DF_STATIC_TLS': 0x0010 } elf_d_tag = { # from 'DT_NULL': 0, 'DT_NEEDED': 1, 'DT_PLTRELSZ': 2, 'DT_PLTGOT': 3, 'DT_HASH': 4, 'DT_STRTAB': 5, 'DT_SYMTAB': 6, 'DT_RELA': 7, 'DT_RELASZ': 8, 'DT_RELAENT': 9, 'DT_STRSZ': 10, 'DT_SYMENT': 11, 'DT_INIT': 12, 'DT_FINI': 13, 'DT_SONAME': 14, 'DT_RPATH': 15, 'DT_SYMBOLIC': 16, 'DT_REL': 17, 'DT_RELSZ': 18, 'DT_RELENT': 19, 'DT_PLTREL': 20, 'DT_DEBUG': 21, 'DT_TEXTREL': 22, 'DT_JMPREL': 23, 'DT_BIND_NOW': 24, 'DT_INIT_ARRAY': 25,'DT_FINI_ARRAY': 26, 'DT_INIT_ARRAYSZ': 27, 'DT_FINI_ARRAYSZ': 28, 'DT_RUNPATH': 29, 'DT_FLAGS': 30, 'DT_ENCODING': 32, 'DT_PREINIT_ARRAY': 32, 'DT_PREINIT_ARRAYSZ': 33, 'DT_LOOS': 0x6000000d, 'DT_HIOS': 0x6ffff000, 'DT_LOPROC': 0x70000000, 'DT_HIPROC': 0x7fffffff, 'DT_SUNW_AUXILIARY': 0x6000000D, 'DT_SUNW_RTLDINF': 0x6000000E, 'DT_SUNW_FILTER': 0x6000000F, 'DT_SUNW_CAP': 0x60000010, # from "usr.bin/elfdump/elfdump.c" 'DT_GNU_PRELINKED': 0x6ffffdf5, 'DT_GNU_CONFLICTSZ': 0x6ffffdf6, 'DT_GNU_LIBLISTSZ': 0x6ffffdf7, 'DT_SUNW_CHECKSUM': 0x6ffffdf78, 'DT_PLTPADSZ': 0x6ffffdf79, 'DT_MOVEENT': 0x6ffffdfa, 'DT_MOVESZ': 0x6ffffdfb, 'DT_FEATURE': 0x6ffffdfc, 'DT_FEATURE': 0x6ffffdfd, 'DT_POSFLAG_1': 0x6ffffdfe, 'DT_SYMINENT': 0x6ffffdff, 'DT_VALRNGHI': 0x6ffffdff, # dup 'DT_ADDRRNGLO': 0x6ffffe00, 'DT_GNU_CONFLICT': 0x6ffffef8, 'DT_GNU_LIBLIST': 0x6ffffef9, 'DT_SUNW_CONFIG': 0x6ffffefa, 'DT_SUNW_DEPAUDIT': 0x6ffffefb, 'DT_SUNW_AUDIT': 0x6ffffefc, 'DT_SUNW_PLTPAD': 0x6ffffefd, 'DT_SUNW_MOVETAB': 0x6ffffefe, 'DT_SYMINFO': 0x6ffffeff, 'DT_ADDRRNGHI': 0x6ffffeff, # dup 'DT_VERSYM': 0x6ffffff0, 'DT_GNU_VERSYM': 0x6ffffff0, # dup 'DT_RELACOUNT': 0x6ffffff9, 'DT_RELCOUNT': 0x6ffffffa, 'DT_FLAGS_1': 0x6ffffffb, 'DT_VERDEF': 0x6ffffffc, 'DT_VERDEFNUM': 0x6ffffffd, 'DT_VERNEED': 0x6ffffffe, 'DT_VERNEEDNUM': 0x6fffffff, 'DT_IA_64_PLT_RESERVE': 0x70000000, 'DT_SUNW_AUXILIARY': 0x7ffffffd, 'DT_SUNW_USED': 0x7ffffffe, 'DT_SUNW_FILTER': 0x7fffffff } elf_dyn_fields = [ 'd_tag', 'd_val', 'd_ptr' ] elf_ehdr_flags = { # no known flags } elf_ehdr_type = { # e_type 'ET_NONE': 0, 'ET_REL': 1, 'ET_EXEC': 2, 'ET_DYN': 3, 'ET_CORE': 4 } elf_ehdr_machine = { # e_machine 'EM_NONE': 0, 'EM_M32': 1, 'EM_SPARC': 2, 'EM_386': 3, 'EM_68K': 4, 'EM_88K': 5, 'EM_486': 6, 'EM_860': 7, 'EM_MIPS': 8, 'EM_S370': 9, 'EM_MIPS_RS3_LE': 10, 'EM_MIPS_RS4_BE': 10, 'EM_PARISC': 15, 'EM_VPP500': 17, 'EM_SPARC32PLUS': 18, 'EM_960': 19, 'EM_PPC': 20, 'EM_PPC64': 21, 'EM_S390': 22, 'EM_V800': 36, 'EM_FR20': 37, 'EM_RH32': 38, 'EM_RCE': 39, 'EM_ARM': 40, 'EM_ALPHA_STD': 41, 'EM_SH': 42, 'EM_SPARCV9': 43, 'EM_TRICORE': 44, 'EM_ARC': 45, 'EM_H8_300': 46, 'EM_H8_300H': 47, 'EM_H8S': 48, 'EM_H8_500': 49, 'EM_IA_64': 50, 'EM_MIPS_X': 51, 'EM_COLDFIRE': 52, 'EM_68HC12': 53, 'EM_MMA': 54, 'EM_PCP': 55, 'EM_NCPU': 56, 'EM_NDR1': 57, 'EM_STARCORE': 58, 'EM_ME16': 59, 'EM_ST100': 60, 'EM_TINYJ': 61, 'EM_X86_64': 62, 'EM_ALPHA': 0x9026 } elf_ei_version = { # e_version 'EV_NONE': 0, 'EV_CURRENT': 1 } elf_ei_class = { 'ELFCLASSNONE': 0, 'ELFCLASS32': 1, 'ELFCLASS64': 2 } elf_ei_data = { 'ELFDATANONE': 0, 'ELFDATA2LSB': 1, 'ELFDATA2MSB': 2 } elf_ei_osabi = { # Official values. 'ELFOSABI_NONE': 0, 'ELFOSABI_HPUX': 1, 'ELFOSABI_NETBSD': 2, 'ELFOSABI_GNU': 3, 'ELFOSABI_HURD': 4, 'ELFOSABI_86OPEN': 5, 'ELFOSABI_SOLARIS': 6, 'ELFOSABI_AIX': 7, 'ELFOSABI_IRIX': 8, 'ELFOSABI_FREEBSD': 9, 'ELFOSABI_TRU64': 10, 'ELFOSABI_MODESTO': 11, 'ELFOSABI_OPENBSD': 12, 'ELFOSABI_OPENVMS': 13, 'ELFOSABI_NSK': 14, 'ELFOSABI_ARM': 97, 'ELFOSABI_STANDALONE': 255, # Aliases. 'ELFOSABI_SYSV': 0, 'ELFOSABI_LINUX': 3, 'ELFOSABI_MONTEREY': 7 } elf_ph_fields = [ 'p_align', 'p_filesz', 'p_flags', 'p_memsz', 'p_offset', 'p_paddr', 'p_type', 'p_vaddr' ] elf_ph_flags = { 'PF_X': 0x1, 'PF_W': 0x2, 'PF_R': 0x4 } elf_ph_type = { 'PT_NULL': 0, 'PT_LOAD': 1, 'PT_DYNAMIC': 2, 'PT_INTERP': 3, 'PT_NOTE': 4, 'PT_SHLIB': 5, 'PT_PHDR': 6, 'PT_TLS': 7, 'PT_LOOS': 0x60000000, 'PT_HIOS': 0x6FFFFFFF, 'PT_SUNW_UNWIND': 0x6464E550, 'PT_GNU_EHFRAME': 0x6464E550, # dup 'PT_SUNWBSS': 0x6FFFFFFA, 'PT_SUNWSTACK': 0x6FFFFFFB, 'PT_SUNWDTRACE': 0x6FFFFFFC, 'PT_SUNWCAP': 0x6FFFFFFD, 'PT_LOPROC': 0x70000000, 'PT_HIPROC': 0x7FFFFFFF } elf_sh_type = { 'SHT_NULL': 0, 'SHT_PROGBITS': 1, 'SHT_SYMTAB': 2, 'SHT_STRTAB': 3, 'SHT_RELA': 4, 'SHT_HASH': 5, 'SHT_DYNAMIC': 6, 'SHT_NOTE': 7, 'SHT_NOBITS': 8, 'SHT_REL': 9, 'SHT_SHLIB': 10, 'SHT_DYNSYM': 11, 'SHT_INIT_ARRAY': 14, 'SHT_FINI_ARRAY': 15, 'SHT_PREINIT_ARRAY': 16, 'SHT_GROUP': 17, 'SHT_SYMTAB_SHNDX': 18, 'SHT_LOOS': 0x60000000, 'SHT_HIOS': 0x6fffffff, 'SHT_LOPROC': 0x70000000, 'SHT_HIPROC': 0x7fffffff, 'SHT_LOUSER': 0x80000000, 'SHT_HIUSER': 0xffffffff, # OS specific types 'SHT_SUNW_dof': 0x6FFFFFF4, 'SHT_SUNW_cap': 0x6FFFFFF5, 'SHT_SUNW_SIGNATURE': 0x6FFFFFF6, 'SHT_SUNW_ANNOTATE': 0x6FFFFFF7, 'SHT_GNU_LIBLIST': 0x6ffffff7, # dup 'SHT_SUNW_DEBUGSTR': 0x6FFFFFF8, 'SHT_SUNW_DEBUG': 0x6FFFFFF9, 'SHT_SUNW_move': 0x6FFFFFFA, 'SHT_SUNW_COMDAT': 0x6FFFFFFB, 'SHT_SUNW_syminfo': 0x6FFFFFFC, 'SHT_GNU_verdef': 0x6ffffffd, 'SHT_SUNW_verdef': 0x6ffffffd, # dup 'SHT_GNU_verneed': 0x6ffffffe, 'SHT_SUNW_verneed': 0x6ffffffe, # dup 'SHT_GNU_versym': 0x6fffffff, 'SHT_SUNW_versym': 0x6fffffff, # dup # Processor specific types 'SHT_IA_64_EXT': 0x70000000, 'SHT_IA_64_UNWIND': 0x70000001 } elf_sh_flags = { 'SHF_WRITE': 0x1, 'SHF_ALLOC': 0x2, 'SHF_EXECINSTR': 0x4, 'SHF_MERGE': 0x10, 'SHF_STRINGS': 0x20, 'SHF_INFO_LINK': 0x40, 'SHF_LINK_ORDER': 0x80, 'SHF_OS_NONCONFORMING': 0x100, 'SHF_GROUP': 0x200, 'SHF_TLS': 0x400, 'SHF_MASKOS': 0x0ff00000, 'SHF_MASKPROC': 0xf0000000 } elf_st_bindings = { 'STB_LOCAL': 0, 'STB_GLOBAL': 1, 'STB_WEAK': 2 } elf_st_flags = { 'SHF_WRITE': 1, 'SHF_ALLOC': 2, 'SHF_EXECINSTR': 4 } elf_st_types = { 'STT_NOTYPE': 0, 'STT_OBJECT': 1, 'STT_FUNC': 2, 'STT_SECTION': 3, 'STT_FILE': 3 } elf_syminfo_flags = { 'SYMINFO_FLG_DIRECT': 1, 'SYMINFO_FLG_PASSTHRU': 2, 'SYMINFO_FLG_FILTER': 2, # dup 'SYMINFO_FLG_COPY': 4, 'SYMINFO_FLG_LAZYLOAD': 8, 'SYMINFO_FLG_DIRECTBIND': 0x10, 'SYMINFO_FLG_NOEXTDIRECT': 0x20, 'SYMINFO_FLG_AUXILIARY': 0x40 } elf_syminfo_boundto_types = { 'SYMINFO_BT_SELF': 0xFFFF, 'SYMINFO_BT_PARENT': 0xFFFE, 'SYMINFO_BT_NONE': 0xFFFD, 'SYMINFO_BT_EXTERN': 0xFFFC } # Defaults defaults = { # ElfDyn structures 'd_tag': 'DT_NULL', 'd_un': '0', # fields in an ELf Executable Header 'e_ehsize': None, 'e_entry': '0', 'e_flags': [ '0' ], 'e_ident': None, 'e_machine': 'EM_NONE', 'e_phentsize': None, 'e_phnum': None, 'e_phoff': None, 'e_shentsize': None, 'e_shnum': None, 'e_shoff': None, 'e_shstrndx': None, 'e_type': 'ET_NONE', 'e_version': 'EV_CURRENT', # e_ident bytes 'ei_class': 'ELFCLASS32', 'ei_data': 'ELFDATA2LSB', 'ei_version': 'EV_CURRENT', 'ei_osabi': 'ELFOSABI_NONE', 'ei_abiversion': '0', # File-wide defaults 'elf_fillchar': '0', # Elf Notes 'n_namesz': None, 'n_descsz': None, 'n_type': '0', 'n_data': [ "", "" ], # Phdr 'p_align': '1', 'p_filesz': '0', 'p_memsz': '0', 'p_flags': [ '0' ], 'p_offset': '0', 'p_paddr': '0', 'p_type': 'PT_NULL', 'p_vaddr': '0', # Shdr 'sh_addr': '0', 'sh_addralign': None, 'sh_data': [], 'sh_entsize': '0', 'sh_flags': [ '0' ], 'sh_info': '0', 'sh_index': None, 'sh_link': '0', 'sh_name': '0', 'sh_offset': None, 'sh_size': None, 'sh_type': 'SHT_NULL', # Verdaux 'vda_name': 0, 'vda_next': 0, # Verdef 'vd_version': 1, 'vd_flags': 0, 'vd_ndx': 0, 'vd_cnt': 0, 'vd_hash': 0, 'vd_aux': 0, 'vd_next': 0, # Vernaux 'vna_hash': 0, 'vna_flags': 0, 'vna_other': 0, 'vna_name': 0, 'vna_next': 0, # Verneed 'vn_version': 1, 'vn_cnt': 0, 'vn_file': 0, 'vn_aux': 0, 'vn_next': 0 } # # Module wide constants. # ELFCLASS32 = elf_ei_class['ELFCLASS32'] ELFDATA2LSB = elf_ei_data['ELFDATA2LSB'] SHT_NOBITS = elf_sh_type['SHT_NOBITS'] SHT_NULL = elf_sh_type['SHT_NULL'] SHT_STRTAB = elf_sh_type['SHT_STRTAB'] SHN_LORESERVE= 0xFF00 SHN_XINDEX = 0xFFFF # # Helper functions. # def get(d, key, default): """Retrieve the value of 'key' from YAML dictionary 'd'. The return value is guaranteed to be not 'None'. """ v = d.get(key, default) if v is None: v = default return v def encode(d, key, default, mapping): """Return the numeric value of d[key] in map 'mapping'.""" v = get(d, key, default) try: return mapping[v] except KeyError: return int(v) def encode_flags(flags, m): """Convert 'flags' to a single numeric value using mapping 'm'.""" try: v = long(flags) return v except: pass v = 0L for f in flags: try: t = long(m[f]) except KeyError: t = long(f) v |= t return v def check_dict(d, l, node=None): """Check a dictionary for unknown keys.""" unknown = [] for k in d.keys(): if k not in l: unknown.append(k) if len(unknown) > 0: raise ElfError(node, "{%s} Unknown key(s) %s" % \ (node.tag, unknown)) # # Helper classes. # class ElfStrTab: """A ELF string table. This class manages strings in an ELF string table section. """ def __init__(self, strs=None): """Initialize a string table from a list of strings.""" self.offset = 1 # reserve space for initial null byte self.htab = {} if type(strs) == types.StringType: # one string self.add(strs) elif type(strs) == types.ListType: # list of strings for s in strs: self.add(s) def add(self, str): """Add a string to the string table. Returns the offset of the string in the ELF section.""" try: return self.lookup(str) except KeyError: self.htab[str] = offset = self.offset self.offset += len(str) + 1 # Keep space for a NUL. return offset def bits(self): """Return the contents of an ELF string table.""" l = self.htab.items() l.sort(lambda x, y: cmp(x[1],y[1])) # Order by string offset. ls = [""] # initial NUL for (ss,oo) in l: ls.append(ss) return "\000".join(ls) + "\000" # Add trailing NULs def lookup(self, str): """Return the ELF string table offset for string 'str'.""" return self.htab[str] class ElfType: """A base type for ELF type descriptors. Derived classes are expected to provide the following attributes: 'fields' -- a list of 4-typles (name, fn, lsz, msz). 'name' is the name of a field in the ELF structure. 'fn' is a convertor function, one of the functions 'do_(long,encode,flags)' below. 'msz' and 'lsz' provide the appropriate sizes when generating a binary representation of the type. """ fields = None def __init__(self, d, node): """Initialize an ELF datatype from a YAML description. Arguments: d -- a dictionary containing name/value pairs specified in the text description. node -- YAML parser node for this element. """ keys = map(lambda t: t[0], self.fields) check_dict(d, keys, node) for f in self.fields: name = f[0] fn = f[1] try: v = fn(d, name) setattr(self,f[0],v) except: raise ElfError(node, 'key: "%s" value: "%s" unrecognized.' % \ (name, d[name])) self._n = node # Save YAML node and associated value self._d = d # for error reporting. def __getitem__(self, attrib): """Allow an ELF type to be treated like a dictionary.""" return getattr(self, attrib) def bits(self, formatchar, elfclass): """Convert an ELF type to its file representation.""" format, args = self.getfields(elfclass) return struct.pack(formatchar + format, *args) def formatstring(self, elfclass): """Return the format string for this type.""" if elfclass == ELFCLASS32: n = 2 else: n = 3 return "".join(map (lambda t: t[n], self.fields)) def content(self, elfclass): """Return a tuple containing the values for an ELF type.""" a = [] if elfclass == ELFCLASS32: n = 2 else: n = 3 for t in self.fields: if t[n] != "": a.append(getattr(self, t[0])) return tuple(a) def getfields(self, elfclass): """Describe the binary layout of the type. Return a tuple (formatstring, *args) describing the desired binary layout in the manner of the 'struct' python library module. """ return (self.formatstring(elfclass), self.content(elfclass)) def layout(self, offset, elf): """Perform any layout-time translation for an ELF type.""" return offset def size(self, elfclass): """Return the size of the type in bytes. The size returned is independent of the alignment needs of the type. """ format = self.formatstring(elfclass) sz = 0 for f in format: if f == "B": sz += 1 elif f == "H": sz += 2 elif f == "I": sz += 4 elif f == "Q": sz += 8 elif f == "": pass else: raise TypeError, "Invalid format char '%s'." % f return sz # # Translation helper functions. # def do_string(d, n): """Convert a YAML value to a Python string.""" v = get(d, n, defaults[n]) if v: return str(v) return v def do_long(d, n): """Convert a YAML value to a Python 'long'.""" v = get(d, n, defaults[n]) if v: return long(v) return v def do_copy(d, n): """Copy a YAML value without conversion.""" v = get(d, n, defaults[n]) return v def do_encode(xlate): """Translate a YAML value according to mapping 'xlate'.""" return lambda d, n, xl=xlate: encode(d, n, defaults[n], xl) def do_flags(xlate): """Translate a list of flags according to mapping 'xlate'.""" return lambda d, n, xl=xlate: encode_flags(get(d, n, defaults[n]), xl) # # Definitions of ELF types. # class ElfCap(ElfType): """A representation of an ELF Cap structure. YAML tag: !Cap """ fields = [ ('c_tag', do_encode(elf_cap_tag), "I", "Q"), ('c_un', do_long, "I", "Q") ] def __init__(self, cap, node): ElfType.__init__(self, cap, node) class ElfDyn(ElfType): """A representation of an ELF Dyn structure. YAML tag: !Dyn """ fields = [ ('d_tag', do_encode(elf_d_tag), "I", "Q"), ('d_un', do_long, "I", "Q") ] def __init__(self, d, node): ElfType.__init__(self, d, node) class ElfEhdrIdent(ElfType): """A representation for the 'ident' field of an ELF Ehdr. YAML tag: !Ident """ fields = [ ('ei_class', do_encode(elf_ei_class), "B", "B"), ('ei_data', do_encode(elf_ei_data), "B", "B"), ('ei_version', do_encode(elf_ei_version), "B", "B"), ('ei_osabi', do_encode(elf_ei_osabi), "B", "B"), ('ei_abiversion', do_long, "B", "B") ] def __init__(self, ei, node): ElfType.__init__(self, ei, node) def bits(self, format, elfclass): f, args = self.getfields(elfclass) s = "\x7FELF" s += struct.pack(f + 'xxxxxxx', *args) return s class ElfEhdr(ElfType): """A representation of an ELF Executable Header. YAML tag: !Ehdr """ fields = [ ('e_ident', do_copy, "", ""), ('e_type', do_encode(elf_ehdr_type), "H", "H"), ('e_machine', do_encode(elf_ehdr_machine), "H", "H"), ('e_version', do_encode(elf_ei_version), "I", "I"), ('e_entry', do_long, "I", "Q"), ('e_phoff', do_long, "I", "Q"), ('e_shoff', do_long, "I", "Q"), ('e_flags', do_flags(elf_ehdr_flags), "I", "I"), ('e_ehsize', do_long, "H", "H"), ('e_phentsize', do_long, "H", "H"), ('e_phnum', do_long, "H", "H"), ('e_shentsize', do_long, "H", "H"), ('e_shnum', do_long, "H", "H"), ('e_shstrndx', do_copy, "H", "H") ] def __init__(self, eh, node): """Initialize an Ehdr structure. If an 'ident' structure was not specified as part of the YAML description, initialize it explicitly. """ ElfType.__init__(self, eh, node) if self.e_ident is None: self.e_ident = ElfEhdrIdent({}, node) def layout(self, offset, elf): """Layout an ELF Ehdr. This method will fill in defaults and/or compute values for fields that were not specified in the YAML description. """ elfclass = elf.elfclass() if elfclass == ELFCLASS32: e_ehsize = 52 e_phentsize = 32 e_shentsize = 40 alignment = 4 else: # 64 bit sizes e_ehsize = 64 e_phentsize = 56 e_shentsize = 64 alignment = 8 if self.e_ehsize is None: self.e_ehsize = e_ehsize # Compute e_phnum if needed. if self.e_phnum is None: self.e_phnum = len(elf.elf_phdrtab) # Compute a value for the e_phentsize field. if self.e_phentsize is None: if self.e_phnum: self.e_phentsize = e_phentsize else: self.e_phentsize = 0 # Set the e_shentsize field. if self.e_shentsize is None: self.e_shentsize = e_shentsize # The program header defaults to just after the ELF header. if self.e_phoff is None: if self.e_phnum > 0: self.e_phoff = \ (self.e_ehsize + (alignment - 1)) & \ ~(alignment - 1) else: self.e_phoff = 0 # compute e_shnum self.nsections = elf.elf_sections.get_shnum() if self.nsections > 0: if self.e_shstrndx is None: self.e_shstrndx = '.shstrtab' if type(self.e_shstrndx) == types.StringType: self.e_shstrndx = \ elf.elf_sections.get_index(self.e_shstrndx) elif type(self.e_shstrndx) == types.IntType or \ type(self.e_shstrndx) == types.LongType: pass else: raise ElfError(self._n, "Unparseable e_shstrndx field.") if self.e_shstrndx is None: raise ElfError(self._n, 'Cannot determine section ' + \ 'name string table index.') else: if self.e_shstrndx is None: self.e_shstrndx = 0 if self.e_shnum is None: self.e_shnum = self.nsections # section data comes after the program header by default. The # section header table is placed after all section data. if self.e_phnum > 0: offset = self.e_phoff + self.e_phnum * self.e_phentsize else: offset = self.e_ehsize offset = elf.elf_sections.layout(offset, elf) if self.e_shoff is None: if self.nsections > 0: self.e_shoff = (offset + (alignment-1)) & \ ~(alignment-1) else: self.e_shoff = 0 if self.nsections >= SHN_LORESERVE: elf.elf_sections.set_extended_shnum(self.nsections) self.e_shnum = 0 if self.e_shstrndx >= SHN_XINDEX: elf.elf_sections.set_extended_shstrndx(self.e_shstrndx) self.e_shstrndx = SHN_XINDEX def bits(self, formatchar, elfclass): """Return the file representation of an Elf Ehdr.""" s = self.e_ident.bits(formatchar, elfclass) s += ElfType.bits(self, formatchar, elfclass) return s class ElfLong: """Wrapper around a python Int/Long.""" def __init__(self, v): self._v = long(v) def bits(self, formatchar, elfclass): """Return the file representation for this object. Depending on the number of bits needed to represent the number, the returned bits would be either 4 or 8 bytes wide. """ if self._v > 0xFFFFFFFFL: f = formatchar + "Q" else: f = formatchar + "I" return struct.pack(f, self._v) class ElfMove(ElfType): """A representation of an Elf Move type. YAML tag: !Move """ fields = [ ('m_value', do_long, "I", "I"), ('m_info', do_long, "I", "Q"), ('m_poffset', do_long, "I", "Q"), ('m_repeat', do_long, "H", "H"), ('m_stride', do_long, "H", "H") ] def __init__(self, move, node): ElfType.__init__(self, move, node) class ElfNote(ElfType): """A representation of an Elf Note type. YAML tag: !Note The data in the note is held in YAML node named 'n_data' which is a pair of strings, one for the note's name field and one for the description. If the fields 'n_namesz' and 'n_descz' aren't specified, they are computed from the contents of 'n_data'. """ fields = [ ('n_namesz', do_long, "I", "I"), ('n_descsz', do_long, "I", "I"), ('n_type', do_long, "I", "I"), ('n_data', do_copy, "", "") ] def __init__(self, note, node): ElfType.__init__(self, note, node) self._note = note def layout(self, offset, elfclass): if len(self.n_data) != 2: raise ElfError(node, "Note data not a pair of strings.") for nd in self.n_data: if isinstance(nd, ElfType): nd.layout(offset, elfclass) if self.n_namesz is None: self.n_namesz = len(self.n_data[0]) if self.n_descsz is None: self.n_descsz = len(self.n_data[1]) def bits(self, format, elfclass): b = ElfType.bits(self, format, elfclass) nbits = str(self.n_data[0]) dbits = str(self.n_data[1]) return b + nbits + dbits class ElfPhdr(ElfType): """A representation of an ELF Program Header Table entry. YAML tag: !Phdr """ fields = [ # NOTE: class-dependent field ordering ('p_align', do_long), ('p_filesz', do_long), ('p_flags' , do_flags(elf_ph_flags), ), ('p_memsz' , do_long), ('p_offset', do_long), ('p_paddr' , do_long), ('p_type' , do_encode(elf_ph_type)), ('p_vaddr' , do_long) ] def __init__(self, ph, node): ElfType.__init__(self, ph, node) def to_string(self): """Helper during debugging.""" s = "Phdr(type:%(p_type)d,flags:%(p_flags)d," \ "offset:%(p_offset)ld,vaddr:%(p_vaddr)ld," \ "paddr:%(p_paddr)ld,filesz:%(p_filesz)ld," \ "memsz:%(p_memsz)ld)" % self return s def bits(self, formatchar, elfclass): """Return the file representation of a Phdr.""" f = formatchar # Phdr structures are laid out in a class-dependent way if elfclass == ELFCLASS32: f += "IIIIIIII" s = struct.pack(f, self.p_type, self.p_offset, self.p_vaddr, self.p_paddr, self.p_filesz, self.p_memsz, self.p_flags, self.p_align) else: f += "IIQQQQQQ" s = struct.pack(f, self.p_type, self.p_flags, self.p_offset, self.p_vaddr, self.p_paddr, self.p_filesz, self.p_memsz, self.p_align) return s class ElfRel(ElfType): """A representation of an ELF Rel type. YAML tag: !Rel """ fields = [ ('r_offset', do_long, "I", "Q"), ('r_info', do_long, "I", "Q") ] def __init__(self, rel, node): ElfType.__init__(self, rel, node) class ElfRela(ElfType): """A representation of an ELF Rela type. YAML tag: !Rela """ fields = [ ('r_offset', do_long, "I", "Q"), ('r_info', do_long, "I", "Q"), ('r_addend', do_long, "I", "Q") ] def __init__(self, rela, node): ElfType.__init__(self, rela, node) class ElfSection(ElfType): """A representation of an ELF Section. YAML tag: !Section A section description consists of the fields that make up an ELF section header entry and an additional field 'sh_data' that contains the data associated with this section. 'sh_data' may be a YAML string, or a YAML list of items that comprise the content of the section. """ fields = [ ('sh_name', do_string, "I", "I"), ('sh_type', do_encode(elf_sh_type), "I", "I"), ('sh_flags', do_flags(elf_sh_flags), "I", "Q"), ('sh_addr', do_long, "I", "Q"), ('sh_offset', do_long, "I", "Q"), ('sh_size', do_long, "I", "Q"), ('sh_link', do_long, "I", "I"), ('sh_info', do_long, "I", "I"), ('sh_addralign', do_copy, "I", "Q"), ('sh_entsize', do_long, "I", "Q"), ('sh_data', do_copy, "", ""), ('sh_index', do_long, "", "") ] def __init__(self, shdr, node): """Initialize a section descriptor.""" ElfType.__init__(self, shdr, node) if type(self.sh_data) != types.ListType: self.sh_data = list(self.sh_data) if self.sh_addralign is None: if self.sh_type == SHT_NULL or self.sh_type == SHT_NOBITS: self.sh_addralign = 0 else: self.sh_addralign = 1 else: if (self.sh_addralign == 0 or \ (self.sh_addralign & (self.sh_addralign - 1)) != 0): raise ElfError(node, "'sh_addralign' not a power of two.") self._data = None # 'cache' of translated data self._strtab = None def to_string(self): """Helper function during debugging.""" return "Section(name:%(sh_name)s,type:%(sh_type)d," \ "flags:%(sh_flags)x,addr:%(sh_addr)d,"\ "offset:%(sh_offset)d,size:%(sh_size)d," \ "link:%(sh_link)d,info:%(sh_info)d," \ "addralign:%(sh_addralign)d,entsize:%(sh_entsize)d)" % \ self def make_strtab(self): """Create a string table from section contents.""" self._strtab = ElfStrTab(self.sh_data) def string_to_index(self, name): """Convert 'name' to an offset inside a string table. Only valid for sections of type SHT_STRTAB.""" if self._strtab: return self._strtab.lookup(name) raise ElfError(None, 'Cannot translate "%s" to an index.' % name) def bits(self, formatchar, elfclass): raise AssertionError, "Section objects should use " \ "databits() or headerbits()" def layout(self, offset, elf): """Prepare an ELF section for output.""" if type(self.sh_name) == types.StringType: # first try convert it to a long try: self.sh_name = long(self.sh_name) except ValueError: # lookup in string table try: self.sh_name = \ elf.section_name_index(self.sh_name) except KeyError: raise ElfError(self._n, "Section name '%s' not in string table." % \ self.sh_name) # give a chance for the contents of a section to xlate strings for d in self.sh_data: if isinstance(d, ElfType): d.layout(offset, elf) # compute the space used by the section data self._data = self.databits(elf.formatchar(), elf.elfclass()) align = self.sh_addralign if align == 0: align = 1 if self.sh_type == SHT_NULL or self.sh_type == SHT_NOBITS: isnulltype = 1 else: isnulltype = 0 offset = (offset + (align - 1)) & ~(align - 1) if self.sh_size is None: if isnulltype: self.sh_size = 0 else: self.sh_size = len(self._data) if self.sh_offset is None: if isnulltype: self.sh_offset = 0 else: self.sh_offset = offset if isnulltype: # ignore bits for null types return offset return offset + len(self._data) def databits(self, formatchar, elfclass): """Return the contents of a section.""" if self._data: return self._data # special-case string table handling if self.sh_type == SHT_STRTAB: return self._strtab.bits() # 'normal' section s = "" for d in self.sh_data: if isinstance(d, ElfType): s += d.bits(formatchar, elfclass) elif isinstance(d, types.LongType): s += struct.pack(formatchar + "Q", d) elif isinstance(d, types.IntType): s += struct.pack(formatchar + "I", d) else: s += str(d) return s def headerbits(self, formatchar, elfclass): """Return the file representation of the section header.""" return ElfType.bits(self, formatchar, elfclass) class ElfSym(ElfType): """A representation for an ELF Symbol type. YAML tag: !Sym """ fields = [ # NOTE: class-dependent layout. ('st_info', do_long, "B", "B"), ('st_name', do_string, "I", "I"), ('st_other', do_long, "B", "B"), ('st_shndx', do_string, "H", "H"), ('st_size', do_long, "I", "Q"), ('st_value', do_long, "I", "Q") ] def __init__(self, sym, node): ElfType.__init__(self, sym, node) def bits(self, format, elfclass): """Return the file representation for an ELF Sym.""" if elfclass == ELFCLASS32: s = struct.pack(format + "IIIBBH", self.st_name, self.st_value, self.st_size, self.st_info, self.st_other, self.st_shndx) else: s = struct.pack(format + "IBBHQQ", self.st_name, self.st_info, self.st_other, self.st_shndx, self.st_value, self.st_size) return s def layout(self, offset, elf): """Perform layout-time conversions for an ELF Sym. String valued fields are converted to offsets into string tables. """ if type(self.st_shndx) == types.StringType: self.st_shndx = \ elf.elf_sections.get_index(self.st_shndx) if self.st_shndx is None: raise ElfError(self._n, "Untranslateable 'st_shndx' " + \ "value \"%s\"." % self.st_shndx) if type(self.st_name) == types.StringType: try: strtab = \ elf.elf_sections[self.st_shndx]._strtab except IndexError: raise ElfError(self._n, "'st_shndx' out of range") if strtab is None: raise ElfError(self._n, "'st_shndx' not of type STRTAB.") try: self.st_name = strtab.lookup(self.st_name) except KeyError: raise ElfError(self._n, 'unknown string "%s"' % self.st_name) return offset class ElfSyminfo(ElfType): """A representation of an ELF Syminfo type. YAML tag: !Syminfo """ fields = [ ('si_boundto', do_encode(elf_syminfo_boundto_types), "H", "H"), ('si_flags', do_flags(elf_syminfo_flags), "H", "H") ] def __init__(self, syminfo, node): ElfType.__init__(self, syminfo, node) class ElfVerdaux(ElfType): """A representation of an ELF Verdaux type.""" fields = [ ('vda_name', do_long, "I", "I"), ('vda_next', do_long, "I", "I") ] def __init__(self, verdaux, node): ElfType.__init__(self, verdaux, node) class ElfVerdef(ElfType): """A representation of an ELF Verdef type.""" fields = [ ('vd_version', do_long, "H", "H"), ('vd_flags', do_long, "H", "H"), ('vd_ndx', do_long, "H", "H"), ('vd_cnt', do_long, "H", "H"), ('vd_hash', do_long, "I", "I"), ('vd_aux', do_long, "I", "I"), ('vd_next', do_long, "I", "I") ] def __init__(self, verdef, node): ElfType.__init__(self, verdef, node) class ElfVernaux(ElfType): """A representation of an ELF Vernaux type.""" fields = [ ('vna_hash', do_long, "I", "I"), ('vna_flags', do_long, "H", "H"), ('vna_other', do_long, "H", "H"), ('vna_name', do_long, "I", "I"), ('vna_next', do_long, "I", "I") ] def __init__(self, vernaux, node): ElfType.__init__(self, vernaux, node) class ElfVerneed(ElfType): """A representation of an ELF Verneed type.""" fields = [ ('vn_version', do_long, "H", "H"), ('vn_cnt', do_long, "H", "H"), ('vn_file', do_long, "I", "I"), ('vn_aux', do_long, "I", "I"), ('vn_next', do_long, "I", "I") ] def __init__(self, verneed, node): ElfType.__init__(self, verneed, node) # # Aggregates # class ElfPhdrTable: """A representation of an ELF Program Header Table. A program header table is a list of program header entry sections. """ def __init__(self, phdr): """Initialize a program header table object. Argument 'phdr' is a list of parsed ElfPhdr objects. """ self.pht_data = [] for ph in phdr: if type(ph) == types.DictType: ph = ElfPhdr(ph) elif not isinstance(ph, ElfPhdr): raise ElfError(ph.node, "Program Header Table " "contains non header data.") self.pht_data.append(ph) def bits(self, formatchar, elfclass): """Return the file representation of the Phdr table.""" s = "" for d in self.pht_data: s += d.bits(formatchar, elfclass) return s def __len__(self): """Return the number of program header table entries.""" return len(self.pht_data) def __iter__(self): """Return an iterator for traversing Phdr entries.""" return self.pht_data.__iter__() class ElfSectionList: """A list of ELF sections.""" def __init__(self, shlist): """Initialize an ELF section list. Argument 'shlist' is a list of parser ElfSection objects. """ self.shl_sections = shlist self.shl_sectionnames = [] self.shl_nentries = len(shlist) for sh in shlist: if not isinstance(sh, ElfSection): raise ElfError(None, """Section 'sections' contains unrecognized data.""") if sh.sh_index is not None: if self.shl_nentries <= sh.sh_index: self.shl_nentries = sh.sh_index + 1 self.shl_sectionnames.append((sh.sh_name, sh.sh_index)) if sh.sh_type == SHT_STRTAB: # a string table sh.make_strtab() def __len__(self): """Return the number of ELF sections.""" return len(self.shl_sections) def __iter__(self): """Iterate through ELF sections.""" return self.shl_sections.__iter__() def __getitem__(self, ind): """Retrieve the ELF section at index 'ind'.""" try: return self.shl_sections[ind] except IndexError: for sh in self.shl_sections: if sh.sh_index == ind: return sh raise IndexError, "no section at index %d" % ind def layout(self, offset, elf): """Compute the layout for section.""" if len(self.shl_sections) == 0: return 0 for sh in self.shl_sections: # layout sections offset = sh.layout(offset, elf) return offset def get_index(self, name): """Return the section index for section 'name', or 'None'.""" c = 0 for (n,i) in self.shl_sectionnames: if n == name: if i is None: return c else: return i c += 1 return None def get_shnum(self): """Retrieve the number of sections in this container.""" return self.shl_nentries def set_extended_shnum(self, shnum): """Set the extended section number.""" sh = self.shl_sections[0] sh.sh_size = shnum def set_extended_shstrndx(self, strndx): """Set the extended string table index.""" sh = self.shl_sections[0] sh.sh_link = strndx class Elf: """A representation of an ELF object.""" def __init__(self, yamldict, ehdr, phdrtab, sections): self._d = yamldict self._n = None self.elf_ehdr = ehdr self.elf_phdrtab = phdrtab self.elf_sections = sections self.elf_fillchar = long(get(yamldict, 'elf_fillchar', defaults['elf_fillchar'])) def byteorder(self): """Return the byteorder for this ELF object.""" return self.elf_ehdr.e_ident.ei_data def elfclass(self): """Return the ELF class for this ELF object.""" return self.elf_ehdr.e_ident.ei_class def formatchar(self): """Return the format character corresponding to the ELF byteorder.""" if self.byteorder() == ELFCLASS32: return "<" else: return ">" def layout(self): """Compute a file layout for this ELF object and update internal data structures.""" self.elf_ehdr.layout(0, self) def section_name_index(self, name): """Compute index of section 'name' in the section name string table.""" strndx = self.elf_ehdr.e_shstrndx if strndx is None: return None return self.elf_sections[strndx].string_to_index(name) def write(self, fn): """Write out the file representation of an ELF object. Argument 'fn' denotes the destination.""" of = file(fn, 'w') formatchar = self.formatchar() elfclass = self.elfclass() # Write out the header of.write(self.elf_ehdr.bits(formatchar, elfclass)) # Write out the program header table if present if self.elf_phdrtab: self.reposition(of, self.elf_ehdr.e_phoff) for ph in self.elf_phdrtab: of.write(ph.bits(formatchar, elfclass)) # Write out the sections if self.elf_sections: # First the contents of the sections for sh in self.elf_sections: if sh.sh_type == SHT_NULL or sh.sh_type == SHT_NOBITS: continue self.reposition(of, sh.sh_offset) of.write(sh.databits(formatchar, elfclass)) # Then the header table self.reposition(of, self.elf_ehdr.e_shoff) for sh in self.elf_sections: if sh.sh_index: new_offset = sh.sh_index * self.elf_ehdr.e_shentsize + \ self.elf_ehdr.e_shoff self.reposition(of, new_offset) of.write(sh.headerbits(formatchar, elfclass)) of.close() def reposition(self, f, offset): """Reposition file `f' to offset `offset', filling gaps with the configured fill character as needed.""" pos = f.tell() if offset == pos: return if offset < pos or (offset > pos and self.elf_fillchar == 0): f.seek(offset, 0) return s = ("%c" % self.elf_fillchar) * (offset - pos) f.write(s) # # YAML Parser configuration and helpers. # yaml_tags = [ (u'!Cap', ElfCap), (u'!Dyn', ElfDyn), (u'!Ehdr', ElfEhdr), (u'!Ident', ElfEhdrIdent), (u'!Move', ElfMove), (u'!Note', ElfNote), (u'!Phdr', ElfPhdr), (u'!Rel', ElfRel), (u'!Rela', ElfRela), (u'!Section', ElfSection), (u'!Sym', ElfSym), (u'!Syminfo', ElfSyminfo), (u'!Verdaux', ElfVerdaux), (u'!Verdef', ElfVerdef), (u'!Vernaux', ElfVernaux), (u'!Verneed', ElfVerneed) ] def init_parser(): for t in yaml_tags: yaml.add_constructor(t[0], # lamdba: loader, node, class lambda l, n, c=t[1]: \ c(l.construct_mapping(n, deep=True), n)) def make_elf(yd): """Convert a YAML description `yd' of an ELF file into an ELF object.""" try: eh = yd['ehdr'] except KeyError: eh = ElfEhdr({}, None) phdrtab = ElfPhdrTable(get(yd, 'phdrtab', {})) sectionlist = ElfSectionList(get(yd, 'sections', {})) return Elf(yd, eh, phdrtab, sectionlist) # # MAIN # if __name__ == '__main__': parser = optparse.OptionParser(usage=usage, version=version, description=description) parser.add_option("-o", "--output", dest="output", help="write output to FILE [default: %default]", metavar="FILE", default="a.out") parser.add_option("-N", "--no-shstrtab", dest="do_shstrtab", help="do not create a string table section for " "section names if missing", action="store_false", metavar="BOOLEAN", default=True) parser.add_option("-U", "--no-shnundef", dest="do_shnundef", help="do not create a section header for index " "SHN_UNDEF if missing", action="store_false", metavar="BOOLEAN", default=True) (options, args) = parser.parse_args() if len(args) > 1: parser.error("only one input-file must be specified") try: if args: stream = file(args[0], 'r') else: stream = sys.stdin except IOError, x: parser.error("cannot open stream: %s" % x) init_parser() try: elf = make_elf(yaml.load(stream)) elf.layout() elf.write(options.output) except yaml.YAMLError, x: parser.error("cannot parse stream: %s" % x) except ElfError, msg: print msg sys.exit(1) # Local Variables: # mode: python # tab-width: 4 # py-indent-offset: 4 # End: