.. it can find symbols and determine how to load a binary This might be useful for CircuitPython "coproc" so that an elf file can be used (instead of a bin file); symbol offsets within the shared memory area can be determined, and the right portion of the elf file can be loaded into the coprocessor memory.
141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
import struct
|
|
from collections import namedtuple
|
|
|
|
|
|
class StructMixin:
|
|
@classmethod
|
|
def calcsize(cls):
|
|
return struct.calcsize(cls._fmt)
|
|
|
|
@classmethod
|
|
def frombuffer(cls, buf):
|
|
return cls(*struct.unpack(cls._fmt, buf))
|
|
|
|
_ElfHeader32 = namedtuple('_ElfHeader32', """
|
|
e_ident e_type e_machine e_version e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum e_shentsize e_shnum e_shstrndx""".split())
|
|
class ElfHeader32(_ElfHeader32, StructMixin):
|
|
_fmt='<16s2h5l6h'
|
|
|
|
_SectionHeader32 = namedtuple('_SectionHeader32', """
|
|
sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info
|
|
sh_addralign sh_entsize
|
|
""".split())
|
|
class SectionHeader32(_SectionHeader32, StructMixin):
|
|
_fmt = '<10l'
|
|
|
|
class Section:
|
|
def __init__(self, ef, sh):
|
|
self._elffile = ef
|
|
self._header = sh
|
|
|
|
def readat(self, offset, sz):
|
|
return self._elffile._readat(offset + self._header.sh_offset, sz)
|
|
def constructat(self, offset, cls):
|
|
return self._elffile._constructat(offset + self._header.sh_offset, cls)
|
|
|
|
class StringTable(Section):
|
|
def symbolat(self, offset):
|
|
result = b''
|
|
stream = self._elffile.stream
|
|
stream.seek(self._header.sh_offset + offset)
|
|
while (c := stream.read(1)) != b'\0' and c != b'':
|
|
result += c
|
|
return result
|
|
|
|
_SymbolTableEntry = namedtuple('_SymbolTableEntry',
|
|
['st_name', 'st_value', 'st_size', 'set_info', 'st_other', 'st_shndx'])
|
|
|
|
class SymbolTableEntry(_SymbolTableEntry, StructMixin):
|
|
_fmt = '<3l2bh'
|
|
|
|
class Symbol:
|
|
def __init__(self, name, entry):
|
|
self.name = name
|
|
self.entry = entry
|
|
|
|
class SymbolTable(Section):
|
|
def iter_symbols(self):
|
|
for i in range(0, self._header.sh_size, SymbolTableEntry.calcsize()):
|
|
yield self.constructat(i, SymbolTableEntry)
|
|
|
|
def get_symbol_by_name(self, name):
|
|
if not isinstance(name, bytes): name = name.encode()
|
|
strs = self._elffile.get_section_by_name('.strtab')
|
|
for sy in self.iter_symbols():
|
|
name2 = strs.symbolat(sy.st_name)
|
|
if name == name2:
|
|
return [Symbol(name, sy)]
|
|
|
|
|
|
section_constructors = {
|
|
2: SymbolTable,
|
|
3: StringTable,
|
|
}
|
|
|
|
_HeaderTableEntry = namedtuple('_HeaderTableEntry',
|
|
['p_type', 'p_offset', 'p_vaddr', 'p_paddr', 'p_filesz', 'p_memsz', 'p_flags', 'p_align'])
|
|
|
|
PT_LOAD = 1
|
|
|
|
class HeaderTableEntry(_HeaderTableEntry, StructMixin):
|
|
_fmt = '<8l'
|
|
|
|
class ELFFile:
|
|
def __init__(self, stream):
|
|
self.stream = stream
|
|
self._buffer = ()
|
|
if self._readat(0, 4) != b'\177ELF':
|
|
raise ValueError("Not an ELF file")
|
|
if self._readat(4, 3) != b'\1\1\1':
|
|
raise ValueError("Incompatible ELF file")
|
|
self._header = self._constructat(0, ElfHeader32)
|
|
|
|
def _readat(self, offset, sz):
|
|
if len(self._buffer) < sz:
|
|
self._buffer = bytearray(sz)
|
|
self._view = memoryview(self._buffer)
|
|
mv = self._view[:sz]
|
|
self.stream.seek(offset)
|
|
self.stream.readinto(mv)
|
|
return mv
|
|
|
|
def _decodeat(self, offset, fmt):
|
|
sz = struct.calcsize(fmt)
|
|
mb = self._readat(offset, sz)
|
|
return struct.unpack(fmt, mv)
|
|
|
|
def _constructat(self, offset, cls):
|
|
sz = cls.calcsize()
|
|
mb = self._readat(offset, sz)
|
|
return cls.frombuffer(mb)
|
|
|
|
def get_section(self, index):
|
|
if not (0 <= index < self._header.e_shnum):
|
|
raise IndexError("Invalid section number")
|
|
offset = self._header.e_shoff + index * self._header.e_shentsize
|
|
sh = self._constructat(offset, SectionHeader32)
|
|
constructor = section_constructors.get(sh.sh_type, Section)
|
|
return constructor(self, sh)
|
|
|
|
def iter_sections(self):
|
|
for i in range(self._header.e_shnum):
|
|
yield self.get_section(i)
|
|
|
|
def get_section_by_name(self, name):
|
|
if not isinstance(name, bytes): name = name.encode()
|
|
idx = self.get_section(self._header.e_shstrndx)
|
|
for sec in self.iter_sections():
|
|
off = sec._header.sh_name
|
|
name2 = idx.symbolat(off)
|
|
if name == name2:
|
|
return sec
|
|
|
|
def get_header(self, index):
|
|
if not (0 <= index < self._header.e_phnum):
|
|
raise IndexError("Invalid header number")
|
|
offset = self._header.e_phoff + index * self._header.e_phentsize
|
|
return self._constructat(offset, HeaderTableEntry)
|
|
|
|
def iter_headers(self):
|
|
for i in range(self._header.e_phnum):
|
|
yield self.get_header(i)
|