#!/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: