mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-12 18:25:33 +00:00
66 lines
2.9 KiB
Python
66 lines
2.9 KiB
Python
"""Small, dependency-free reader for the 32-bit little-endian firmware ELFs."""
|
|
|
|
from pathlib import Path
|
|
import struct
|
|
|
|
|
|
class FirmwareElf:
|
|
def __init__(self, path):
|
|
self.data = Path(path).read_bytes()
|
|
if self.data[:7] != b"\x7fELF\x01\x01\x01" or len(self.data) < 52:
|
|
raise ValueError("expected a 32-bit little-endian firmware ELF")
|
|
offset = self.unpack("I", 32)[0]
|
|
entry_size, count, names_index = self.unpack("HHH", 46)
|
|
if entry_size != 40 or not count or names_index >= count:
|
|
raise ValueError("missing or invalid ELF section table")
|
|
self.sections = [self.unpack("10I", offset + i * entry_size) for i in range(count)]
|
|
self.symbols = {}
|
|
for section in self.sections:
|
|
if section[1] != 2: # SHT_SYMTAB
|
|
continue
|
|
if section[9] != 16 or section[5] % 16 or section[6] >= count:
|
|
raise ValueError("invalid ELF symbol table")
|
|
strings = self.section_bytes(self.sections[section[6]])
|
|
for pos in range(section[4], section[4] + section[5], 16):
|
|
name, value, size, info, _, index = self.unpack("IIIBBH", pos)
|
|
if not index or not name:
|
|
continue
|
|
if name >= len(strings):
|
|
raise ValueError("invalid ELF symbol name")
|
|
name = strings[name:].split(b"\0", 1)[0].decode("ascii")
|
|
# Prefer global/weak definitions to same-named local symbols.
|
|
if name not in self.symbols or info >> 4:
|
|
self.symbols[name] = (value, size, index)
|
|
if not self.symbols:
|
|
raise ValueError("firmware ELF has no defined symbols")
|
|
|
|
def unpack(self, fmt, offset):
|
|
try:
|
|
return struct.unpack_from("<" + fmt, self.data, offset)
|
|
except struct.error as error:
|
|
raise ValueError("truncated firmware ELF") from error
|
|
|
|
def section_bytes(self, section):
|
|
offset, size = section[4:6]
|
|
if offset + size > len(self.data):
|
|
raise ValueError("truncated ELF section")
|
|
return self.data[offset:offset + size]
|
|
|
|
def address(self, name):
|
|
if name not in self.symbols:
|
|
raise ValueError(f"missing ELF symbol {name}")
|
|
return self.symbols[name][0]
|
|
|
|
def read(self, address, size):
|
|
for section in self.sections:
|
|
if section[1] == 8 or not section[2] & 2: # NOBITS / not allocated
|
|
continue
|
|
start, length = section[3], section[5]
|
|
if start <= address and address + size <= start + length:
|
|
offset = address - start
|
|
return self.section_bytes(section)[offset:offset + size]
|
|
raise ValueError(f"ELF has no initialized data at {address:#x} ({size} bytes)")
|
|
|
|
def words(self, name, count=1):
|
|
return struct.unpack("<" + "I" * count, self.read(self.address(name), count * 4))
|