建立 MAG160C 逆向工程交接仓库
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,176 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/constants.py
|
||||
#
|
||||
# Constants and flags, placed into classes for namespacing
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class E_FLAGS:
|
||||
""" Flag values for the e_flags field of the ELF header
|
||||
"""
|
||||
EF_ARM_EABIMASK=0xFF000000
|
||||
EF_ARM_EABI_VER1=0x01000000
|
||||
EF_ARM_EABI_VER2=0x02000000
|
||||
EF_ARM_EABI_VER3=0x03000000
|
||||
EF_ARM_EABI_VER4=0x04000000
|
||||
EF_ARM_EABI_VER5=0x05000000
|
||||
EF_ARM_GCCMASK=0x00400FFF
|
||||
EF_ARM_RELEXEC=0x01
|
||||
EF_ARM_HASENTRY=0x02
|
||||
EF_ARM_SYMSARESORTED=0x04
|
||||
EF_ARM_DYNSYMSUSESEGIDX=0x8
|
||||
EF_ARM_MAPSYMSFIRST=0x10
|
||||
EF_ARM_LE8=0x00400000
|
||||
EF_ARM_BE8=0x00800000
|
||||
EF_ARM_ABI_FLOAT_SOFT=0x00000200
|
||||
EF_ARM_ABI_FLOAT_HARD=0x00000400
|
||||
|
||||
EF_PPC64_ABI_V0=0
|
||||
EF_PPC64_ABI_V1=1
|
||||
EF_PPC64_ABI_V2=2
|
||||
|
||||
EF_MIPS_NOREORDER=1
|
||||
EF_MIPS_PIC=2
|
||||
EF_MIPS_CPIC=4
|
||||
EF_MIPS_XGOT=8
|
||||
EF_MIPS_64BIT_WHIRL=16
|
||||
EF_MIPS_ABI2=32
|
||||
EF_MIPS_ABI_ON32=64
|
||||
EF_MIPS_32BITMODE = 256
|
||||
EF_MIPS_NAN2008=1024
|
||||
EF_MIPS_ARCH=0xf0000000
|
||||
EF_MIPS_ARCH_1=0x00000000
|
||||
EF_MIPS_ARCH_2=0x10000000
|
||||
EF_MIPS_ARCH_3=0x20000000
|
||||
EF_MIPS_ARCH_4=0x30000000
|
||||
EF_MIPS_ARCH_5=0x40000000
|
||||
EF_MIPS_ARCH_32=0x50000000
|
||||
EF_MIPS_ARCH_64=0x60000000
|
||||
EF_MIPS_ARCH_32R2=0x70000000
|
||||
EF_MIPS_ARCH_64R2=0x80000000
|
||||
|
||||
EF_RISCV_RVC=0x00000001
|
||||
EF_RISCV_FLOAT_ABI=0x00000006
|
||||
EF_RISCV_FLOAT_ABI_SOFT=0x00000000
|
||||
EF_RISCV_FLOAT_ABI_SINGLE=0x00000002
|
||||
EF_RISCV_FLOAT_ABI_DOUBLE=0x00000004
|
||||
EF_RISCV_FLOAT_ABI_QUAD=0x00000006
|
||||
EF_RISCV_RVE=0x00000008
|
||||
EF_RISCV_TSO=0x00000010
|
||||
|
||||
EF_LOONGARCH_OBJABI_MASK=0x000000C0
|
||||
EF_LOONGARCH_OBJABI_V0=0x00000000
|
||||
EF_LOONGARCH_OBJABI_V1=0x00000040
|
||||
EF_LOONGARCH_ABI_MODIFIER_MASK=0x00000007
|
||||
EF_LOONGARCH_ABI_SOFT_FLOAT=0x00000001
|
||||
EF_LOONGARCH_ABI_SINGLE_FLOAT=0x00000002
|
||||
EF_LOONGARCH_ABI_DOUBLE_FLOAT=0x00000003
|
||||
# The names in the glibc elf.h say "LARCH" instead of "LOONGARCH",
|
||||
# provide these names for users' convenience.
|
||||
EF_LARCH_OBJABI_MASK = EF_LOONGARCH_OBJABI_MASK
|
||||
EF_LARCH_OBJABI_V0 = EF_LOONGARCH_OBJABI_V0
|
||||
EF_LARCH_OBJABI_V1 = EF_LOONGARCH_OBJABI_V1
|
||||
EF_LARCH_ABI_MODIFIER_MASK = EF_LOONGARCH_ABI_MODIFIER_MASK
|
||||
EF_LARCH_ABI_SOFT_FLOAT = EF_LOONGARCH_ABI_SOFT_FLOAT
|
||||
EF_LARCH_ABI_SINGLE_FLOAT = EF_LOONGARCH_ABI_SINGLE_FLOAT
|
||||
EF_LARCH_ABI_DOUBLE_FLOAT = EF_LOONGARCH_ABI_DOUBLE_FLOAT
|
||||
|
||||
class E_FLAGS_MASKS:
|
||||
"""Masks to be used for convenience when working with E_FLAGS
|
||||
|
||||
This is a simplified approach that is also used by GNU binutils
|
||||
readelf
|
||||
"""
|
||||
EFM_MIPS_ABI = 0x0000F000
|
||||
EFM_MIPS_ABI_O32 = 0x00001000
|
||||
EFM_MIPS_ABI_O64 = 0x00002000
|
||||
EFM_MIPS_ABI_EABI32 = 0x00003000
|
||||
EFM_MIPS_ABI_EABI64 = 0x00004000
|
||||
|
||||
|
||||
class SHN_INDICES:
|
||||
""" Special section indices
|
||||
"""
|
||||
SHN_UNDEF=0
|
||||
SHN_LORESERVE=0xff00
|
||||
SHN_LOPROC=0xff00
|
||||
SHN_HIPROC=0xff1f
|
||||
SHN_ABS=0xfff1
|
||||
SHN_COMMON=0xfff2
|
||||
SHN_HIRESERVE=0xffff
|
||||
SHN_XINDEX=0xffff
|
||||
|
||||
|
||||
class SH_FLAGS:
|
||||
""" Flag values for the sh_flags field of section headers
|
||||
"""
|
||||
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_COMPRESSED=0x800
|
||||
SHF_MASKOS=0x0ff00000
|
||||
SHF_EXCLUDE=0x80000000
|
||||
SHF_MASKPROC=0xf0000000
|
||||
|
||||
|
||||
class RH_FLAGS:
|
||||
""" Flag values for the DT_MIPS_FLAGS dynamic table entries
|
||||
"""
|
||||
RHF_NONE=0x00000000
|
||||
RHF_QUICKSTART=0x00000001
|
||||
RHF_NOTPOT=0x00000002
|
||||
RHF_NO_LIBRARY_REPLACEMENT=0x00000004
|
||||
RHF_NO_MOVE=0x00000008
|
||||
RHF_SGI_ONLY=0x00000010
|
||||
RHF_GUARANTEE_INIT=0x00000020
|
||||
RHF_DELTA_C_PLUS_PLUS=0x00000040
|
||||
RHF_GUARANTEE_START_INIT=0x00000080
|
||||
RHF_PIXIE=0x00000100
|
||||
RHF_DEFAULT_DELAY_LOAD=0x00000200
|
||||
RHF_REQUICKSTART=0x00000400
|
||||
RHF_REQUICKSTARTED=0x00000800
|
||||
RHF_CORD=0x00001000
|
||||
RHF_NO_UNRES_UNDEF=0x00002000
|
||||
RHF_RLD_ORDER_SAFE=0x00004000
|
||||
|
||||
|
||||
class P_FLAGS:
|
||||
""" Flag values for the p_flags field of program headers
|
||||
"""
|
||||
PF_X=0x1
|
||||
PF_W=0x2
|
||||
PF_R=0x4
|
||||
PF_MASKOS=0x00FF0000
|
||||
PF_MASKPROC=0xFF000000
|
||||
|
||||
|
||||
# symbol info flags for entries
|
||||
# in the .SUNW_syminfo section
|
||||
class SUNW_SYMINFO_FLAGS:
|
||||
""" Flags for the si_flags field of entries
|
||||
in the .SUNW_syminfo section
|
||||
"""
|
||||
SYMINFO_FLG_DIRECT=0x1
|
||||
SYMINFO_FLG_FILTER=0x2
|
||||
SYMINFO_FLG_COPY=0x4
|
||||
SYMINFO_FLG_LAZYLOAD=0x8
|
||||
SYMINFO_FLG_DIRECTBIND=0x10
|
||||
SYMINFO_FLG_NOEXTDIRECT=0x20
|
||||
SYMINFO_FLG_AUXILIARY=0x40
|
||||
SYMINFO_FLG_INTERPOSE=0x80
|
||||
SYMINFO_FLG_CAP=0x100
|
||||
SYMINFO_FLG_DEFERRED=0x200
|
||||
|
||||
class VER_FLAGS:
|
||||
VER_FLG_BASE=0x1
|
||||
VER_FLG_WEAK=0x2
|
||||
VER_FLG_INFO=0x4
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,402 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/dynamic.py
|
||||
#
|
||||
# ELF Dynamic Tags
|
||||
#
|
||||
# Mike Frysinger (vapier@gentoo.org)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from collections import defaultdict
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, Protocol, TypedDict, cast, runtime_checkable
|
||||
|
||||
from ..common.exceptions import ELFError
|
||||
from ..common.utils import elf_assert, struct_parse, parse_cstring_from_stream
|
||||
from .enums import ENUM_D_TAG
|
||||
from .hash import ELFHashTable, GNUHashTable
|
||||
from .relocation import RelocationTable, RelrRelocationTable
|
||||
from .sections import Section, Symbol
|
||||
from .segments import Segment
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
|
||||
|
||||
class RelocationTables(TypedDict, total=False):
|
||||
REL: RelocationTable
|
||||
RELA: RelocationTable
|
||||
RELR: RelrRelocationTable
|
||||
JMPREL: RelocationTable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class _StringTable(Protocol):
|
||||
"""Common base-class of elftools.elf.dynamic._DynamicStringTable and
|
||||
elftools.elf.section.StringTableSection to be consumed by
|
||||
DynamicTag|Dynamic.
|
||||
Requires @runtime_checkable as `assert isinstance(…, _StringTable)` is
|
||||
used."""
|
||||
def get_string(self, offset: int, /) -> str: ...
|
||||
|
||||
|
||||
class _DynamicStringTable:
|
||||
""" Bare string table based on values found via ELF dynamic tags and
|
||||
loadable segments only. Good enough for get_string() only.
|
||||
"""
|
||||
def __init__(self, stream: IO[bytes], table_offset: int) -> None:
|
||||
self._stream = stream
|
||||
self._table_offset = table_offset
|
||||
|
||||
def get_string(self, offset: int) -> str:
|
||||
""" Get the string stored at the given offset in this string table.
|
||||
"""
|
||||
s = parse_cstring_from_stream(self._stream, self._table_offset + offset)
|
||||
return s.decode('utf-8') if s else ''
|
||||
|
||||
|
||||
class DynamicTag:
|
||||
""" Dynamic Tag object - representing a single dynamic tag entry from a
|
||||
dynamic section.
|
||||
|
||||
Allows dictionary-like access to the dynamic structure. For special
|
||||
tags (those listed in the _HANDLED_TAGS set below), creates additional
|
||||
attributes for convenience. For example, .soname will contain the actual
|
||||
value of DT_SONAME (fetched from the dynamic symbol table).
|
||||
"""
|
||||
_HANDLED_TAGS = frozenset(
|
||||
['DT_NEEDED', 'DT_RPATH', 'DT_RUNPATH', 'DT_SONAME',
|
||||
'DT_SUNW_FILTER'])
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entry: Container,
|
||||
stringtable: _StringTable | None,
|
||||
) -> None:
|
||||
if stringtable is None:
|
||||
raise ELFError('Creating DynamicTag without string table')
|
||||
self.entry = entry
|
||||
if entry.d_tag in self._HANDLED_TAGS:
|
||||
setattr(self, entry.d_tag[3:].lower(),
|
||||
stringtable.get_string(self.entry.d_val))
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<DynamicTag (%s): %r>' % (self.entry.d_tag, self.entry)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.entry.d_tag in self._HANDLED_TAGS:
|
||||
s = '"%s"' % getattr(self, self.entry.d_tag[3:].lower())
|
||||
else:
|
||||
s = '%#x' % self.entry.d_ptr
|
||||
return '<DynamicTag (%s) %s>' % (self.entry.d_tag, s)
|
||||
|
||||
|
||||
class Dynamic:
|
||||
""" Shared functionality between dynamic sections and segments.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
elffile: ELFFile,
|
||||
stringtable: _StringTable | Section | None,
|
||||
position: int,
|
||||
empty: bool,
|
||||
) -> None:
|
||||
"""
|
||||
stream:
|
||||
The file-like object from which to load data
|
||||
|
||||
elffile:
|
||||
The parent elffile object
|
||||
|
||||
stringtable:
|
||||
A stringtable reference to use for parsing string references in
|
||||
entries
|
||||
|
||||
position:
|
||||
The file offset of the dynamic segment/section
|
||||
|
||||
empty:
|
||||
Whether this is a degenerate case with zero entries. Normally, every
|
||||
dynamic table will have at least one entry, the DT_NULL terminator.
|
||||
"""
|
||||
self.elffile = elffile
|
||||
self.elfstructs = elffile.structs
|
||||
self._stream = stream
|
||||
self._num_tags = -1 if not empty else 0
|
||||
self._offset = position
|
||||
self._tagsize = self.elfstructs.Elf_Dyn.sizeof()
|
||||
self._empty = empty
|
||||
|
||||
# Do not access this directly yourself; use _get_stringtable() instead.
|
||||
self._stringtable: _StringTable | Section | None = stringtable
|
||||
|
||||
def get_table_offset(self, tag_name: str) -> tuple[int | None, int | None]:
|
||||
""" Return the virtual address and file offset of a dynamic table.
|
||||
"""
|
||||
try:
|
||||
ptr: int = next(
|
||||
tag['d_ptr']
|
||||
for tag in self._iter_tags(type=tag_name)
|
||||
)
|
||||
except StopIteration:
|
||||
return (None, None)
|
||||
|
||||
# If we found a virtual address, locate the offset in the file
|
||||
# by using the program headers.
|
||||
offset = next(self.elffile.address_offsets(ptr), None)
|
||||
|
||||
return ptr, offset
|
||||
|
||||
def _get_stringtable(self) -> _StringTable:
|
||||
""" Return a string table for looking up dynamic tag related strings.
|
||||
|
||||
This won't be a "full" string table object, but will at least
|
||||
support the get_string() function.
|
||||
"""
|
||||
if self._stringtable:
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
# If the ELF has stripped its section table (which is unusual, but
|
||||
# perfectly valid), we need to use the dynamic tags to locate the
|
||||
# dynamic string table.
|
||||
_, table_offset = self.get_table_offset('DT_STRTAB')
|
||||
if table_offset is not None:
|
||||
self._stringtable = _DynamicStringTable(self._stream, table_offset)
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
# That didn't work for some reason. Let's use the section header
|
||||
# even though this ELF is super weird.
|
||||
self._stringtable = self.elffile.get_section_by_name('.dynstr')
|
||||
assert isinstance(self._stringtable, _StringTable)
|
||||
return self._stringtable
|
||||
|
||||
def _iter_tags(self, type: str | None = None) -> Iterator[Container]:
|
||||
""" Yield all raw tags (limit to |type| if specified)
|
||||
"""
|
||||
if self._empty:
|
||||
return
|
||||
for n in itertools.count():
|
||||
tag = self._get_tag(n)
|
||||
if type is None or tag['d_tag'] == type:
|
||||
yield tag
|
||||
if tag['d_tag'] == 'DT_NULL':
|
||||
break
|
||||
|
||||
def iter_tags(self, type: str | None = None) -> Iterator[DynamicTag]:
|
||||
""" Yield all tags (limit to |type| if specified)
|
||||
"""
|
||||
for tag in self._iter_tags(type=type):
|
||||
yield DynamicTag(tag, self._get_stringtable())
|
||||
|
||||
def _get_tag(self, n: int) -> Container:
|
||||
""" Get the raw tag at index #n from the file
|
||||
"""
|
||||
if self._num_tags != -1 and n >= self._num_tags:
|
||||
raise IndexError(n)
|
||||
offset = self._offset + n * self._tagsize
|
||||
return struct_parse(
|
||||
self.elfstructs.Elf_Dyn,
|
||||
self._stream,
|
||||
stream_pos=offset)
|
||||
|
||||
def get_tag(self, n: int) -> DynamicTag:
|
||||
""" Get the tag at index #n from the file (DynamicTag object)
|
||||
"""
|
||||
return DynamicTag(self._get_tag(n), self._get_stringtable())
|
||||
|
||||
def num_tags(self) -> int | None:
|
||||
""" Number of dynamic tags in the file, including the DT_NULL tag
|
||||
"""
|
||||
if self._num_tags != -1:
|
||||
return self._num_tags
|
||||
|
||||
for n in itertools.count():
|
||||
tag = self.get_tag(n)
|
||||
if tag.entry.d_tag == 'DT_NULL':
|
||||
self._num_tags = n + 1
|
||||
return self._num_tags
|
||||
|
||||
return None
|
||||
|
||||
def get_relocation_tables(self) -> RelocationTables:
|
||||
""" Load all available relocation tables from DYNAMIC tags.
|
||||
|
||||
Returns a dictionary mapping found table types (REL, RELA,
|
||||
RELR, JMPREL) to RelocationTable objects.
|
||||
"""
|
||||
|
||||
result: RelocationTables = {}
|
||||
|
||||
if list(self.iter_tags('DT_REL')):
|
||||
result['REL'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_REL')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELSZ'))['d_val'], False)
|
||||
|
||||
relentsz = next(self.iter_tags('DT_RELENT'))['d_val']
|
||||
elf_assert(result['REL'].entry_size == relentsz,
|
||||
'Expected DT_RELENT to be %s' % relentsz)
|
||||
|
||||
if list(self.iter_tags('DT_RELA')):
|
||||
result['RELA'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_RELA')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELASZ'))['d_val'], True)
|
||||
|
||||
relentsz = next(self.iter_tags('DT_RELAENT'))['d_val']
|
||||
elf_assert(result['RELA'].entry_size == relentsz,
|
||||
'Expected DT_RELAENT to be %s' % relentsz)
|
||||
|
||||
if list(self.iter_tags('DT_RELR')):
|
||||
result['RELR'] = RelrRelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_RELR')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_RELRSZ'))['d_val'],
|
||||
next(self.iter_tags('DT_RELRENT'))['d_val'])
|
||||
|
||||
if list(self.iter_tags('DT_JMPREL')):
|
||||
result['JMPREL'] = RelocationTable(self.elffile,
|
||||
self.get_table_offset('DT_JMPREL')[1], # type: ignore[arg-type] # ty: ignore[invalid-argument-type]
|
||||
next(self.iter_tags('DT_PLTRELSZ'))['d_val'],
|
||||
next(self.iter_tags('DT_PLTREL'))['d_val'] == ENUM_D_TAG['DT_RELA'])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class DynamicSection(Section, Dynamic):
|
||||
""" ELF dynamic table section. Knows how to process the list of tags.
|
||||
"""
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
stringtable = elffile.get_section(header['sh_link'], ('SHT_STRTAB', 'SHT_NOBITS', 'SHT_NULL'))
|
||||
Dynamic.__init__(self, self.stream, self.elffile, stringtable,
|
||||
self['sh_offset'], self['sh_type'] == 'SHT_NOBITS')
|
||||
|
||||
|
||||
class DynamicSegment(Segment, Dynamic):
|
||||
""" ELF dynamic table segment. Knows how to process the list of tags.
|
||||
"""
|
||||
def __init__(self, header: Container, stream: IO[bytes], elffile: ELFFile) -> None:
|
||||
# The string table section to be used to resolve string names in
|
||||
# the dynamic tag array is the one pointed at by the sh_link field
|
||||
# of the dynamic section header.
|
||||
# So we must look for the dynamic section contained in the dynamic
|
||||
# segment, we do so by searching for the dynamic section whose content
|
||||
# is located at the same offset as the dynamic segment
|
||||
stringtable = next(
|
||||
(
|
||||
elffile.get_section(section['sh_link'])
|
||||
for section in elffile.iter_sections()
|
||||
if isinstance(section, DynamicSection) and section['sh_offset'] == header['p_offset']
|
||||
),
|
||||
None,
|
||||
)
|
||||
Segment.__init__(self, header, stream)
|
||||
Dynamic.__init__(self, stream, elffile, stringtable, self['p_offset'],
|
||||
self['p_filesz'] == 0)
|
||||
self._symbol_size = self.elfstructs.Elf_Sym.sizeof()
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table recovered from DT_SYMTAB
|
||||
"""
|
||||
return self._num_symbols
|
||||
|
||||
@cached_property
|
||||
def _num_symbols(self) -> int:
|
||||
# Check if a DT_GNU_HASH tag exists and recover the number of symbols
|
||||
# from the corresponding hash table
|
||||
_, gnu_hash_offset = self.get_table_offset('DT_GNU_HASH')
|
||||
if gnu_hash_offset is not None:
|
||||
gnu_hash_section = GNUHashTable(self.elffile, gnu_hash_offset, self)
|
||||
return gnu_hash_section.get_number_of_symbols()
|
||||
|
||||
# If DT_GNU_HASH did not exist, maybe we can use DT_HASH
|
||||
_, hash_offset = self.get_table_offset('DT_HASH')
|
||||
if hash_offset is not None:
|
||||
# Get the hash table from the DT_HASH offset
|
||||
hash_section = ELFHashTable(self.elffile, hash_offset, None, self)
|
||||
return hash_section.get_number_of_symbols()
|
||||
|
||||
# Find closest higher pointer than tab_ptr. We'll use that to mark
|
||||
# the end of the symbol table.
|
||||
tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
|
||||
if tab_ptr is None or tab_offset is None:
|
||||
raise ELFError('Segment does not contain DT_SYMTAB.')
|
||||
|
||||
nearest_ptr: int | None = None
|
||||
for tag in self.iter_tags():
|
||||
tag_ptr = tag['d_ptr']
|
||||
if tag['d_tag'] == 'DT_SYMENT':
|
||||
if self._symbol_size != tag['d_val']:
|
||||
# DT_SYMENT is the size of one symbol entry. It must be
|
||||
# the same as returned by Elf_Sym.sizeof.
|
||||
raise ELFError('DT_SYMENT (%d) != Elf_Sym (%d).' %
|
||||
(tag['d_val'], self._symbol_size))
|
||||
if (tag_ptr > tab_ptr and
|
||||
(nearest_ptr is None or nearest_ptr > tag_ptr)):
|
||||
nearest_ptr = tag_ptr
|
||||
|
||||
if nearest_ptr is not None:
|
||||
return (nearest_ptr - tab_ptr) // self._symbol_size
|
||||
|
||||
# Use the end of last segment that contains DT_SYMTAB (or ends on it)
|
||||
for segment in self.elffile.iter_segments(type='PT_LOAD'):
|
||||
start = segment['p_vaddr']
|
||||
end = start + segment['p_filesz']
|
||||
if start <= tab_ptr <= end:
|
||||
nearest_ptr = end
|
||||
|
||||
if nearest_ptr is not None:
|
||||
return (nearest_ptr - tab_ptr) // self._symbol_size
|
||||
|
||||
raise ELFError('Cannot determine the end of DT_SYMTAB.')
|
||||
|
||||
def get_symbol(self, index: int) -> Symbol:
|
||||
""" Get the symbol at index #index from the table (Symbol object)
|
||||
"""
|
||||
tab_ptr, tab_offset = self.get_table_offset('DT_SYMTAB')
|
||||
if tab_ptr is None or tab_offset is None:
|
||||
raise ELFError('Segment does not contain DT_SYMTAB.')
|
||||
|
||||
symbol = struct_parse(
|
||||
self.elfstructs.Elf_Sym,
|
||||
self._stream,
|
||||
stream_pos=tab_offset + index * self._symbol_size)
|
||||
|
||||
string_table = self._get_stringtable()
|
||||
symbol_name = string_table.get_string(symbol["st_name"])
|
||||
|
||||
return Symbol(symbol, symbol_name)
|
||||
|
||||
def get_symbol_by_name(self, name: str) -> list[Symbol] | None:
|
||||
""" Get a symbol(s) by name. Return None if no symbol by the given name
|
||||
exists.
|
||||
"""
|
||||
symnums = self._symbol_name_map.get(name)
|
||||
return [self.get_symbol(i) for i in symnums] if symnums else None
|
||||
|
||||
@cached_property
|
||||
def _symbol_name_map(self) -> dict[str, list[int]]:
|
||||
smap = defaultdict(list)
|
||||
for i, sym in enumerate(self.iter_symbols()):
|
||||
smap[sym.name].append(i)
|
||||
return smap
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all symbols in this dynamic segment. The symbols are usually
|
||||
the same as returned by SymbolTableSection.iter_symbols. However,
|
||||
in stripped binaries, SymbolTableSection might have been removed.
|
||||
This method reads from the mandatory dynamic tag DT_SYMTAB.
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
@@ -0,0 +1,982 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/elffile.py
|
||||
#
|
||||
# ELFFile - main class for accessing ELF files
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import zlib
|
||||
from functools import cached_property
|
||||
from io import BytesIO
|
||||
from typing import IO, TYPE_CHECKING, Any
|
||||
|
||||
from ..common.exceptions import ELFError, ELFParseError
|
||||
from ..common.utils import struct_parse, elf_assert
|
||||
from .structs import ELFStructs
|
||||
from .sections import (
|
||||
Section, StringTableSection, SymbolTableSection,
|
||||
SymbolTableIndexSection, SUNWSyminfoTableSection, NullSection,
|
||||
NoteSection, StabSection, ARMAttributesSection, RISCVAttributesSection)
|
||||
from .dynamic import DynamicSection, DynamicSegment
|
||||
from .relocation import (RelocationSection, RelocationHandler,
|
||||
RelrRelocationSection)
|
||||
from .gnuversions import (
|
||||
GNUVerNeedSection, GNUVerDefSection,
|
||||
GNUVerSymSection)
|
||||
from .segments import Segment, InterpSegment, NoteSegment
|
||||
from ..dwarf.dwarfinfo import DWARFInfo, DebugSectionDescriptor, DwarfConfig
|
||||
from ..ehabi.ehabiinfo import EHABIInfo
|
||||
from .hash import ELFHashSection, GNUHashSection
|
||||
from .constants import SHN_INDICES
|
||||
from ..dwarf.dwarf_util import _file_crc32
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable, Iterator
|
||||
from collections.abc import Container as TContainer
|
||||
from types import TracebackType
|
||||
|
||||
from typing_extensions import Self # 3.11+
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
|
||||
|
||||
class ELFFile:
|
||||
""" Creation: the constructor accepts a stream (file-like object) with the
|
||||
contents of an ELF file.
|
||||
|
||||
Optionally, a stream_loader function can be passed as the second
|
||||
argument. This stream_loader function takes a relative string path to
|
||||
load a supplementary object file, and returns a stream suitable for
|
||||
creating a new ELFFile. Currently, the only such relative file path is
|
||||
obtained from the supplementary object files.
|
||||
|
||||
Accessible attributes:
|
||||
|
||||
stream:
|
||||
The stream holding the data of the file - must be a binary
|
||||
stream (bytes, not string).
|
||||
|
||||
elfclass:
|
||||
32 or 64 - specifies the word size of the target machine
|
||||
|
||||
little_endian:
|
||||
boolean - specifies the target machine's endianness
|
||||
|
||||
elftype:
|
||||
string or int, either known value of E_TYPE enum defining ELF
|
||||
type (e.g. executable, dynamic library or core dump) or integral
|
||||
unparsed value
|
||||
|
||||
header:
|
||||
the complete ELF file header
|
||||
|
||||
e_ident_raw:
|
||||
the raw e_ident field of the header
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
stream_loader: Callable[[str], IO[bytes]] | None = None,
|
||||
) -> None:
|
||||
self.stream = stream
|
||||
self.stream.seek(0, io.SEEK_END)
|
||||
self.stream_len = self.stream.tell()
|
||||
|
||||
self._identify_file()
|
||||
self.structs = ELFStructs(
|
||||
little_endian=self.little_endian,
|
||||
elfclass=self.elfclass)
|
||||
|
||||
self.structs.create_basic_structs()
|
||||
self.header = self._parse_elf_header()
|
||||
self.structs.create_advanced_structs(
|
||||
self['e_type'],
|
||||
self['e_machine'],
|
||||
self['e_ident']['EI_OSABI'])
|
||||
self.stream.seek(0)
|
||||
self.e_ident_raw = self.stream.read(16)
|
||||
|
||||
self.stream_loader = stream_loader
|
||||
|
||||
@classmethod
|
||||
def load_from_path(cls, path: str | bytes) -> ELFFile:
|
||||
"""Takes a local filesystem path accepted by open(), and returns an
|
||||
ELFFile from it, setting up a stream_loader that resolves linked files
|
||||
using normalized string paths relative to the original file.
|
||||
"""
|
||||
stream = open(path, 'rb')
|
||||
return ELFFile(stream, ELFFile.make_relative_loader(os.fsdecode(path)))
|
||||
|
||||
@staticmethod
|
||||
def make_relative_loader(base_path: str) -> Callable[[str], IO[bytes]]:
|
||||
""" Return a function that takes a potentially relative path,
|
||||
resolves it against base_path (str), and opens a file at that.
|
||||
|
||||
ELFFile uses functions like that for resolving DWARF links. The
|
||||
raw bytes parsed from ELF metadata are decoded before calling this
|
||||
loader.
|
||||
"""
|
||||
if not isinstance(base_path, str):
|
||||
raise TypeError('base_path must be str')
|
||||
base_directory = os.path.realpath(os.path.dirname(base_path))
|
||||
|
||||
def loader(rel_path: str) -> IO[bytes]:
|
||||
if not isinstance(rel_path, str):
|
||||
raise TypeError('rel_path must be str')
|
||||
|
||||
if os.path.isabs(rel_path):
|
||||
raise ELFError('External DWARF path must be relative to the ELF file directory.')
|
||||
|
||||
rel_path = os.path.realpath(os.path.join(base_directory, rel_path))
|
||||
# Resolve ".." segments and symlinks before checking that the final
|
||||
# target still lives under the ELF file's directory.
|
||||
if os.path.commonpath([base_directory, rel_path]) != base_directory:
|
||||
raise ELFError('External DWARF path escapes the ELF file directory.')
|
||||
|
||||
return open(rel_path, 'rb')
|
||||
return loader
|
||||
|
||||
def num_sections(self) -> int:
|
||||
""" Number of sections in the file
|
||||
"""
|
||||
if self['e_shoff'] == 0:
|
||||
return 0
|
||||
# From the ELF ABI documentation at
|
||||
# https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html:
|
||||
# "e_shnum normally tells how many entries the section header table
|
||||
# contains. [...] If the number of sections is greater than or equal to
|
||||
# SHN_LORESERVE (0xff00), e_shnum has the value SHN_UNDEF (0) and the
|
||||
# actual number of section header table entries is contained in the
|
||||
# sh_size field of the section header at index 0 (otherwise, the sh_size
|
||||
# member of the initial entry contains 0)."
|
||||
if self['e_shnum'] == 0:
|
||||
section_header = self._get_section_header(0)
|
||||
return section_header['sh_size']
|
||||
return self['e_shnum']
|
||||
|
||||
def get_section(self, n: int, type: TContainer[str] | None = None) -> Section:
|
||||
""" Get the section at index #n from the file (Section object or a
|
||||
subclass)
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if type and section_header.sh_type not in type:
|
||||
raise ELFError("Unexpected section type %s, expected %s" % (section_header['sh_type'], type))
|
||||
return self._make_section(section_header)
|
||||
|
||||
def _get_linked_symtab_section(self, n: int) -> SymbolTableSection:
|
||||
""" Get the section at index #n from the file, throws
|
||||
if it's not a SYMTAB/DYNTAB.
|
||||
Used for resolving section links with target type validation.
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if section_header['sh_type'] not in ('SHT_SYMTAB', 'SHT_DYNSYM'):
|
||||
raise ELFError("Section points at section %d of type %s, expected SHT_SYMTAB/SHT_DYNSYM" % (n, section_header['sh_type']))
|
||||
section = self._make_section(section_header)
|
||||
assert isinstance(section, SymbolTableSection)
|
||||
return section
|
||||
|
||||
def _get_linked_strtab_section(self, n: int) -> StringTableSection:
|
||||
""" Get the section at index #n from the file, throws
|
||||
if it's not a STRTAB.
|
||||
Used for resolving section links with target type validation.
|
||||
"""
|
||||
section_header = self._get_section_header(n)
|
||||
if section_header['sh_type'] != 'SHT_STRTAB':
|
||||
raise ELFError("SHT_SYMTAB section points at section %d of type %s, expected SHT_STRTAB" % (n, section_header['sh_type']))
|
||||
section = self._make_section(section_header)
|
||||
assert isinstance(section, StringTableSection)
|
||||
return section
|
||||
|
||||
def get_section_by_name(self, name: str) -> Section | None:
|
||||
""" Get a section from the file, by name. Return None if no such
|
||||
section exists.
|
||||
"""
|
||||
secnum = self._section_name_map.get(name, None)
|
||||
return None if secnum is None else self.get_section(secnum)
|
||||
|
||||
def get_section_index(self, section_name: str) -> int | None:
|
||||
""" Gets the index of the section by name. Return None if no such
|
||||
section name exists.
|
||||
"""
|
||||
return self._section_name_map.get(section_name, None)
|
||||
|
||||
def has_section(self, section_name: str) -> bool:
|
||||
""" Section existence check by name, without the overhead of parsing if found.
|
||||
"""
|
||||
return section_name in self._section_name_map
|
||||
|
||||
def iter_sections(self, type: str | None = None) -> Iterator[Section]:
|
||||
""" Yield all the sections in the file. If the optional |type|
|
||||
parameter is passed, this method will only yield sections of the
|
||||
given type. The parameter value must be a string containing the
|
||||
name of the type as defined in the ELF specification, e.g.
|
||||
'SHT_SYMTAB'.
|
||||
"""
|
||||
for i in range(self.num_sections()):
|
||||
section = self.get_section(i)
|
||||
if type is None or section['sh_type'] == type:
|
||||
yield section
|
||||
|
||||
def num_segments(self) -> int:
|
||||
""" Number of segments in the file
|
||||
"""
|
||||
# From: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
|
||||
# Section: 4.1.2 Number of Program Headers
|
||||
# If the number of program headers is greater than or equal to
|
||||
# PN_XNUM (0xffff), this member has the value PN_XNUM
|
||||
# (0xffff). The actual number of program header table entries
|
||||
# is contained in the sh_info field of the section header at
|
||||
# index 0.
|
||||
if self['e_phnum'] < 0xffff:
|
||||
return self['e_phnum']
|
||||
else:
|
||||
return self.get_section(0)['sh_info']
|
||||
|
||||
def get_segment(self, n: int) -> Segment:
|
||||
""" Get the segment at index #n from the file (Segment object)
|
||||
"""
|
||||
segment_header = self._get_segment_header(n)
|
||||
return self._make_segment(segment_header)
|
||||
|
||||
def iter_segments(self, type: str | None = None) -> Iterator[Segment]:
|
||||
""" Yield all the segments in the file. If the optional |type|
|
||||
parameter is passed, this method will only yield segments of the
|
||||
given type. The parameter value must be a string containing the
|
||||
name of the type as defined in the ELF specification, e.g.
|
||||
'PT_LOAD'.
|
||||
"""
|
||||
for i in range(self.num_segments()):
|
||||
segment = self.get_segment(i)
|
||||
if type is None or segment['p_type'] == type:
|
||||
yield segment
|
||||
|
||||
def address_offsets(self, start: int, size: int = 1) -> Iterator[int]:
|
||||
""" Yield a file offset for each ELF segment containing a memory region.
|
||||
|
||||
A memory region is defined by the range [start...start+size). The
|
||||
offset of the region is yielded.
|
||||
"""
|
||||
end = start + size
|
||||
# consider LOAD only to prevent same address being yielded twice
|
||||
for seg in self.iter_segments(type='PT_LOAD'):
|
||||
if (start >= seg['p_vaddr'] and
|
||||
end <= seg['p_vaddr'] + seg['p_filesz']):
|
||||
yield start - seg['p_vaddr'] + seg['p_offset']
|
||||
|
||||
def has_dwarf_info(self, strict: bool = False) -> bool:
|
||||
""" Check whether this file appears to have debugging information.
|
||||
We assume that if it has the .debug_info or .zdebug_info section, it
|
||||
has all the other required sections as well.
|
||||
|
||||
Unless you pass strict=True, the presence of .eh_frame section,
|
||||
which is DWARF adjacent but hardly DWARF proper, will count as debug info.
|
||||
Stripped files contain .eh_frame but none of the .[z]debug_xxx sections.
|
||||
"""
|
||||
return (self.has_section('.debug_info') or
|
||||
self.has_section('.zdebug_info') or
|
||||
(not strict and self.has_section('.eh_frame')))
|
||||
|
||||
def get_dwarf_info(
|
||||
self,
|
||||
relocate_dwarf_sections: bool = True,
|
||||
follow_links: bool = True,
|
||||
) -> DWARFInfo:
|
||||
""" Return a DWARFInfo object representing the debugging information in
|
||||
this file.
|
||||
|
||||
If relocate_dwarf_sections is True, relocations for DWARF sections
|
||||
are looked up and applied.
|
||||
|
||||
If follow_links is True, we will try to load the external and/or supplementary
|
||||
object file (if any), and use it to resolve references and imports.
|
||||
"""
|
||||
# Expect that has_dwarf_info() was called, so at least .debug_info is
|
||||
# present.
|
||||
# Sections that aren't found will be passed as None to DWARFInfo.
|
||||
|
||||
# TODO: support linking by build ID
|
||||
# https://sourceware.org/gdb/current/onlinedocs/gdb.html/Separate-Debug-Files.html
|
||||
|
||||
# A file may contain a debug link but not be stripped, so check for debug_info just in case
|
||||
debuglink_section = self.get_section_by_name('.gnu_debuglink')
|
||||
if debuglink_section and not self.has_dwarf_info(True) and follow_links and self.stream_loader:
|
||||
debuglink = struct_parse(self.structs.Gnu_debuglink, debuglink_section.stream, debuglink_section.header.sh_offset)
|
||||
with self.stream_loader(os.fsdecode(debuglink.filename)) as ext_file:
|
||||
# Validate checksum...
|
||||
if _file_crc32(ext_file) != debuglink.checksum:
|
||||
raise ELFError('The linked DWARF file does not match the checksum in the link.')
|
||||
ext_file.seek(0, os.SEEK_SET)
|
||||
ext_elffile = ELFFile(ext_file, self.stream_loader)
|
||||
# Inheriting the stream loader like that might be wrong if the supplementary DWARF link in the other file
|
||||
# is relative to the other file's directory as opposed to this file's directory.
|
||||
return ext_elffile.get_dwarf_info(relocate_dwarf_sections=relocate_dwarf_sections, follow_links=True)
|
||||
|
||||
section_names = ['.debug_info', '.debug_aranges', '.debug_abbrev',
|
||||
'.debug_str', '.debug_line', '.debug_frame',
|
||||
'.debug_loc', '.debug_ranges', '.debug_pubtypes',
|
||||
'.debug_pubnames', '.debug_addr',
|
||||
'.debug_str_offsets', '.debug_line_str',
|
||||
'.debug_loclists', '.debug_rnglists',
|
||||
'.debug_sup', '.gnu_debugaltlink', '.debug_types',
|
||||
]
|
||||
|
||||
compressed = self.has_section('.zdebug_info')
|
||||
if compressed:
|
||||
section_names = [f'.z{s[1:]}' for s in section_names]
|
||||
|
||||
# As it is loaded in the process image, .eh_frame cannot be compressed
|
||||
section_names.append('.eh_frame')
|
||||
|
||||
(debug_info_sec_name, debug_aranges_sec_name, debug_abbrev_sec_name,
|
||||
debug_str_sec_name, debug_line_sec_name, debug_frame_sec_name,
|
||||
debug_loc_sec_name, debug_ranges_sec_name, debug_pubtypes_name,
|
||||
debug_pubnames_name, debug_addr_name, debug_str_offsets_name,
|
||||
debug_line_str_name, debug_loclists_sec_name, debug_rnglists_sec_name,
|
||||
debug_sup_name, gnu_debugaltlink_name, debug_types_sec_name,
|
||||
eh_frame_sec_name) = section_names
|
||||
|
||||
debug_sections: dict[str, DebugSectionDescriptor | None] = {}
|
||||
for secname in section_names:
|
||||
section = self.get_section_by_name(secname)
|
||||
if section is None:
|
||||
debug_sections[secname] = None
|
||||
else:
|
||||
dwarf_section = self._read_dwarf_section(
|
||||
section,
|
||||
relocate_dwarf_sections)
|
||||
if compressed and secname.startswith('.z'):
|
||||
dwarf_section = self._decompress_dwarf_section(dwarf_section)
|
||||
debug_sections[secname] = dwarf_section
|
||||
|
||||
# Lookup if we have any of the .gnu_debugaltlink (GNU proprietary
|
||||
# implementation) or .debug_sup sections, referencing a supplementary
|
||||
# DWARF file
|
||||
|
||||
dwarfinfo = DWARFInfo(
|
||||
config=DwarfConfig(
|
||||
little_endian=self.little_endian,
|
||||
default_address_size=self.elfclass // 8,
|
||||
machine_arch=self.get_machine_arch()),
|
||||
debug_info_sec=debug_sections[debug_info_sec_name],
|
||||
debug_aranges_sec=debug_sections[debug_aranges_sec_name],
|
||||
debug_abbrev_sec=debug_sections[debug_abbrev_sec_name],
|
||||
debug_frame_sec=debug_sections[debug_frame_sec_name],
|
||||
eh_frame_sec=debug_sections[eh_frame_sec_name],
|
||||
debug_str_sec=debug_sections[debug_str_sec_name],
|
||||
debug_loc_sec=debug_sections[debug_loc_sec_name],
|
||||
debug_ranges_sec=debug_sections[debug_ranges_sec_name],
|
||||
debug_line_sec=debug_sections[debug_line_sec_name],
|
||||
debug_pubtypes_sec=debug_sections[debug_pubtypes_name],
|
||||
debug_pubnames_sec=debug_sections[debug_pubnames_name],
|
||||
debug_addr_sec=debug_sections[debug_addr_name],
|
||||
debug_str_offsets_sec=debug_sections[debug_str_offsets_name],
|
||||
debug_line_str_sec=debug_sections[debug_line_str_name],
|
||||
debug_loclists_sec=debug_sections[debug_loclists_sec_name],
|
||||
debug_rnglists_sec=debug_sections[debug_rnglists_sec_name],
|
||||
debug_sup_sec=debug_sections[debug_sup_name],
|
||||
gnu_debugaltlink_sec=debug_sections[gnu_debugaltlink_name],
|
||||
debug_types_sec=debug_sections[debug_types_sec_name]
|
||||
)
|
||||
if follow_links:
|
||||
dwarfinfo.supplementary_dwarfinfo = self.get_supplementary_dwarfinfo(dwarfinfo)
|
||||
return dwarfinfo
|
||||
|
||||
def has_dwarf_link(self) -> bool:
|
||||
""" Whether the binary's debug info is in an
|
||||
external file. Use get_dwarf_link to retrieve the path to it.
|
||||
"""
|
||||
return self.has_section('.gnu_debuglink')
|
||||
|
||||
def get_dwarf_link(self) -> Container | None:
|
||||
""" Read the .gnu_debuglink section, return an object with filename (as bytes) and checksum (as number) in it.
|
||||
"""
|
||||
section = self.get_section_by_name('.gnu_debuglink')
|
||||
return struct_parse(self.structs.Gnu_debuglink, section.stream, section.header.sh_offset) if section else None
|
||||
|
||||
def get_supplementary_dwarfinfo(self, dwarfinfo: DWARFInfo) -> DWARFInfo | None:
|
||||
"""
|
||||
Read supplementary dwarfinfo, from either the standared .debug_sup
|
||||
section, the GNU proprietary .gnu_debugaltlink, or .gnu_debuglink.
|
||||
"""
|
||||
supfilepath = dwarfinfo.parse_debugsupinfo()
|
||||
if supfilepath is not None and self.stream_loader is not None:
|
||||
stream = self.stream_loader(os.fsdecode(supfilepath))
|
||||
supelffile = ELFFile(stream)
|
||||
dwarf_info = supelffile.get_dwarf_info()
|
||||
stream.close()
|
||||
return dwarf_info
|
||||
return None
|
||||
|
||||
|
||||
def has_ehabi_info(self) -> bool:
|
||||
""" Check whether this file appears to have arm exception handler index table.
|
||||
"""
|
||||
return any(self.iter_sections(type='SHT_ARM_EXIDX'))
|
||||
|
||||
def get_ehabi_infos(self) -> list[EHABIInfo] | None:
|
||||
""" Generally, shared library and executable contain 1 .ARM.exidx section.
|
||||
Object file contains many .ARM.exidx sections.
|
||||
So we must traverse every section and filter sections whose type is SHT_ARM_EXIDX.
|
||||
"""
|
||||
if self['e_type'] == 'ET_REL':
|
||||
# TODO: support relocatable file
|
||||
assert False, "Current version of pyelftools doesn't support relocatable file."
|
||||
_ret = [
|
||||
EHABIInfo(section, self.little_endian)
|
||||
for section in self.iter_sections(type='SHT_ARM_EXIDX')
|
||||
]
|
||||
return _ret if _ret else None
|
||||
|
||||
def get_machine_arch(self) -> str:
|
||||
""" Return the machine architecture, as detected from the ELF header.
|
||||
"""
|
||||
architectures = {
|
||||
'EM_M32' : 'AT&T WE 32100',
|
||||
'EM_SPARC' : 'SPARC',
|
||||
'EM_386' : 'x86',
|
||||
'EM_68K' : 'Motorola 68000',
|
||||
'EM_88K' : 'Motorola 88000',
|
||||
'EM_IAMCU' : 'Intel MCU',
|
||||
'EM_860' : 'Intel 80860',
|
||||
'EM_MIPS' : 'MIPS',
|
||||
'EM_S370' : 'IBM System/370',
|
||||
'EM_MIPS_RS3_LE' : 'MIPS RS3000 Little-endian',
|
||||
'EM_PARISC' : 'Hewlett-Packard PA-RISC',
|
||||
'EM_VPP500' : 'Fujitsu VPP500',
|
||||
'EM_SPARC32PLUS' : 'Enhanced SPARC',
|
||||
'EM_960' : 'Intel 80960',
|
||||
'EM_PPC' : 'PowerPC',
|
||||
'EM_PPC64' : '64-bit PowerPC',
|
||||
'EM_S390' : 'IBM S/390',
|
||||
'EM_SPU' : 'IBM SPU/SPC',
|
||||
'EM_V800' : 'NEC V800',
|
||||
'EM_FR20' : 'Fujitsu FR20',
|
||||
'EM_RH32' : 'TRW RH-32',
|
||||
'EM_RCE' : 'Motorola RCE',
|
||||
'EM_ARM' : 'ARM',
|
||||
'EM_ALPHA' : 'Digital Alpha',
|
||||
'EM_SH' : 'Hitachi SH',
|
||||
'EM_SPARCV9' : 'SPARC Version 9',
|
||||
'EM_TRICORE' : 'Siemens TriCore embedded processor',
|
||||
'EM_ARC' : 'Argonaut RISC Core, Argonaut Technologies Inc.',
|
||||
'EM_H8_300' : 'Hitachi H8/300',
|
||||
'EM_H8_300H' : 'Hitachi H8/300H',
|
||||
'EM_H8S' : 'Hitachi H8S',
|
||||
'EM_H8_500' : 'Hitachi H8/500',
|
||||
'EM_IA_64' : 'Intel IA-64',
|
||||
'EM_MIPS_X' : 'MIPS-X',
|
||||
'EM_COLDFIRE' : 'Motorola ColdFire',
|
||||
'EM_68HC12' : 'Motorola M68HC12',
|
||||
'EM_MMA' : 'Fujitsu MMA',
|
||||
'EM_PCP' : 'Siemens PCP',
|
||||
'EM_NCPU' : 'Sony nCPU',
|
||||
'EM_NDR1' : 'Denso NDR1',
|
||||
'EM_STARCORE' : 'Motorola Star*Core',
|
||||
'EM_ME16' : 'Toyota ME16',
|
||||
'EM_ST100' : 'STMicroelectronics ST100',
|
||||
'EM_TINYJ' : 'Advanced Logic TinyJ',
|
||||
'EM_X86_64' : 'x64',
|
||||
'EM_PDSP' : 'Sony DSP',
|
||||
'EM_PDP10' : 'Digital Equipment PDP-10',
|
||||
'EM_PDP11' : 'Digital Equipment PDP-11',
|
||||
'EM_FX66' : 'Siemens FX66',
|
||||
'EM_ST9PLUS' : 'STMicroelectronics ST9+ 8/16 bit',
|
||||
'EM_ST7' : 'STMicroelectronics ST7 8-bit',
|
||||
'EM_68HC16' : 'Motorola MC68HC16',
|
||||
'EM_68HC11' : 'Motorola MC68HC11',
|
||||
'EM_68HC08' : 'Motorola MC68HC08',
|
||||
'EM_68HC05' : 'Motorola MC68HC05',
|
||||
'EM_SVX' : 'Silicon Graphics SVx',
|
||||
'EM_ST19' : 'STMicroelectronics ST19 8-bit',
|
||||
'EM_VAX' : 'Digital VAX',
|
||||
'EM_CRIS' : 'Axis Communications 32-bit',
|
||||
'EM_JAVELIN' : 'Infineon Technologies 32-bit',
|
||||
'EM_FIREPATH' : 'Element 14 64-bit DSP',
|
||||
'EM_ZSP' : 'LSI Logic 16-bit DSP',
|
||||
'EM_MMIX' : 'Donald Knuth\'s educational 64-bit',
|
||||
'EM_HUANY' : 'Harvard University machine-independent object files',
|
||||
'EM_PRISM' : 'SiTera Prism',
|
||||
'EM_AVR' : 'Atmel AVR 8-bit',
|
||||
'EM_FR30' : 'Fujitsu FR30',
|
||||
'EM_D10V' : 'Mitsubishi D10V',
|
||||
'EM_D30V' : 'Mitsubishi D30V',
|
||||
'EM_V850' : 'NEC v850',
|
||||
'EM_M32R' : 'Mitsubishi M32R',
|
||||
'EM_MN10300' : 'Matsushita MN10300',
|
||||
'EM_MN10200' : 'Matsushita MN10200',
|
||||
'EM_PJ' : 'picoJava',
|
||||
'EM_OPENRISC' : 'OpenRISC 32-bit',
|
||||
'EM_ARC_COMPACT' : 'ARC International ARCompact',
|
||||
'EM_XTENSA' : 'Tensilica Xtensa',
|
||||
'EM_VIDEOCORE' : 'Alphamosaic VideoCore',
|
||||
'EM_TMM_GPP' : 'Thompson Multimedia',
|
||||
'EM_NS32K' : 'National Semiconductor 32000 series',
|
||||
'EM_TPC' : 'Tenor Network TPC',
|
||||
'EM_SNP1K' : 'Trebia SNP 1000',
|
||||
'EM_ST200' : 'STMicroelectronics ST200',
|
||||
'EM_IP2K' : 'Ubicom IP2xxx',
|
||||
'EM_MAX' : 'MAX',
|
||||
'EM_CR' : 'National Semiconductor CompactRISC',
|
||||
'EM_F2MC16' : 'Fujitsu F2MC16',
|
||||
'EM_MSP430' : 'Texas Instruments msp430',
|
||||
'EM_BLACKFIN' : 'Analog Devices Blackfin',
|
||||
'EM_SE_C33' : 'Seiko Epson S1C33',
|
||||
'EM_SEP' : 'Sharp',
|
||||
'EM_ARCA' : 'Arca RISC',
|
||||
'EM_UNICORE' : 'PKU-Unity MPRC',
|
||||
'EM_EXCESS' : 'eXcess',
|
||||
'EM_DXP' : 'Icera Semiconductor Deep Execution Processor',
|
||||
'EM_ALTERA_NIOS2' : 'Altera Nios II',
|
||||
'EM_CRX' : 'National Semiconductor CompactRISC CRX',
|
||||
'EM_XGATE' : 'Motorola XGATE',
|
||||
'EM_C166' : 'Infineon C16x/XC16x',
|
||||
'EM_M16C' : 'Renesas M16C',
|
||||
'EM_DSPIC30F' : 'Microchip Technology dsPIC30F',
|
||||
'EM_CE' : 'Freescale Communication Engine RISC core',
|
||||
'EM_M32C' : 'Renesas M32C',
|
||||
'EM_TSK3000' : 'Altium TSK3000',
|
||||
'EM_RS08' : 'Freescale RS08',
|
||||
'EM_SHARC' : 'Analog Devices SHARC',
|
||||
'EM_ECOG2' : 'Cyan Technology eCOG2',
|
||||
'EM_SCORE7' : 'Sunplus S+core7 RISC',
|
||||
'EM_DSP24' : 'New Japan Radio (NJR) 24-bit DSP',
|
||||
'EM_VIDEOCORE3' : 'Broadcom VideoCore III',
|
||||
'EM_LATTICEMICO32' : 'Lattice FPGA RISC',
|
||||
'EM_SE_C17' : 'Seiko Epson C17',
|
||||
'EM_TI_C6000' : 'TI TMS320C6000',
|
||||
'EM_TI_C2000' : 'TI TMS320C2000',
|
||||
'EM_TI_C5500' : 'TI TMS320C55x',
|
||||
'EM_TI_ARP32' : 'TI Application Specific RISC, 32bit',
|
||||
'EM_TI_PRU' : 'TI Programmable Realtime Unit',
|
||||
'EM_MMDSP_PLUS' : 'STMicroelectronics 64bit VLIW',
|
||||
'EM_CYPRESS_M8C' : 'Cypress M8C',
|
||||
'EM_R32C' : 'Renesas R32C',
|
||||
'EM_TRIMEDIA' : 'NXP Semiconductors TriMedia',
|
||||
'EM_QDSP6' : 'QUALCOMM DSP6',
|
||||
'EM_8051' : 'Intel 8051',
|
||||
'EM_STXP7X' : 'STMicroelectronics STxP7x',
|
||||
'EM_NDS32' : 'Andes Technology RISC',
|
||||
'EM_ECOG1' : 'Cyan Technology eCOG1X',
|
||||
'EM_ECOG1X' : 'Cyan Technology eCOG1X',
|
||||
'EM_MAXQ30' : 'Dallas Semiconductor MAXQ30',
|
||||
'EM_XIMO16' : 'New Japan Radio (NJR) 16-bit',
|
||||
'EM_MANIK' : 'M2000 Reconfigurable RISC',
|
||||
'EM_CRAYNV2' : 'Cray Inc. NV2',
|
||||
'EM_RX' : 'Renesas RX',
|
||||
'EM_METAG' : 'Imagination Technologies META',
|
||||
'EM_MCST_ELBRUS' : 'MCST Elbrus',
|
||||
'EM_ECOG16' : 'Cyan Technology eCOG16',
|
||||
'EM_CR16' : 'National Semiconductor CompactRISC CR16 16-bit',
|
||||
'EM_ETPU' : 'Freescale',
|
||||
'EM_SLE9X' : 'Infineon Technologies SLE9X',
|
||||
'EM_L10M' : 'Intel L10M',
|
||||
'EM_K10M' : 'Intel K10M',
|
||||
'EM_AARCH64' : 'AArch64',
|
||||
'EM_AVR32' : 'Atmel 32-bit',
|
||||
'EM_STM8' : 'STMicroeletronics STM8 8-bit',
|
||||
'EM_TILE64' : 'Tilera TILE64',
|
||||
'EM_TILEPRO' : 'Tilera TILEPro',
|
||||
'EM_MICROBLAZE' : 'Xilinx MicroBlaze 32-bit RISC',
|
||||
'EM_CUDA' : 'NVIDIA CUDA',
|
||||
'EM_TILEGX' : 'Tilera TILE-Gx',
|
||||
'EM_CLOUDSHIELD' : 'CloudShield',
|
||||
'EM_COREA_1ST' : 'KIPO-KAIST Core-A 1st generation',
|
||||
'EM_COREA_2ND' : 'KIPO-KAIST Core-A 2nd generation',
|
||||
'EM_ARC_COMPACT2' : 'Synopsys ARCompact V2',
|
||||
'EM_OPEN8' : 'Open8 8-bit RISC',
|
||||
'EM_RL78' : 'Renesas RL78',
|
||||
'EM_VIDEOCORE5' : 'Broadcom VideoCore V',
|
||||
'EM_78KOR' : 'Renesas 78KOR',
|
||||
'EM_56800EX' : 'Freescale 56800EX',
|
||||
'EM_BA1' : 'Beyond BA1',
|
||||
'EM_BA2' : 'Beyond BA2',
|
||||
'EM_XCORE' : 'XMOS xCORE',
|
||||
'EM_MCHP_PIC' : 'Microchip 8-bit PIC',
|
||||
'EM_INTEL205' : 'Reserved by Intel',
|
||||
'EM_INTEL206' : 'Reserved by Intel',
|
||||
'EM_INTEL207' : 'Reserved by Intel',
|
||||
'EM_INTEL208' : 'Reserved by Intel',
|
||||
'EM_INTEL209' : 'Reserved by Intel',
|
||||
'EM_KM32' : 'KM211 KM32 32-bit',
|
||||
'EM_KMX32' : 'KM211 KMX32 32-bit',
|
||||
'EM_KMX16' : 'KM211 KMX16 16-bit',
|
||||
'EM_KMX8' : 'KM211 KMX8 8-bit',
|
||||
'EM_KVARC' : 'KM211 KVARC',
|
||||
'EM_CDP' : 'Paneve CDP',
|
||||
'EM_COGE' : 'Cognitive',
|
||||
'EM_COOL' : 'Bluechip Systems CoolEngine',
|
||||
'EM_NORC' : 'Nanoradio Optimized RISC',
|
||||
'EM_CSR_KALIMBA' : 'CSR Kalimba',
|
||||
'EM_Z80' : 'Zilog Z80',
|
||||
'EM_VISIUM' : 'VISIUMcore',
|
||||
'EM_FT32' : 'FTDI Chip FT32 32-bit RISC',
|
||||
'EM_MOXIE' : 'Moxie',
|
||||
'EM_AMDGPU' : 'AMD GPU',
|
||||
'EM_RISCV' : 'RISC-V',
|
||||
'EM_BPF' : 'Linux BPF - in-kernel virtual machine',
|
||||
'EM_CSKY' : 'C-SKY',
|
||||
'EM_LOONGARCH' : 'LoongArch',
|
||||
'EM_FRV' : 'Fujitsu FR-V'
|
||||
}
|
||||
|
||||
return architectures.get(self['e_machine'], '<unknown>')
|
||||
|
||||
def get_shstrndx(self) -> int:
|
||||
""" Find the string table section index for the section header table
|
||||
"""
|
||||
# From https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html:
|
||||
# If the section name string table section index is greater than or
|
||||
# equal to SHN_LORESERVE (0xff00), this member has the value SHN_XINDEX
|
||||
# (0xffff) and the actual index of the section name string table section
|
||||
# is contained in the sh_link field of the section header at index 0.
|
||||
if self['e_shstrndx'] != SHN_INDICES.SHN_XINDEX:
|
||||
return self['e_shstrndx']
|
||||
else:
|
||||
section_header = self._get_section_header(0)
|
||||
return section_header['sh_link']
|
||||
|
||||
#-------------------------------- PRIVATE --------------------------------#
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def _identify_file(self) -> None:
|
||||
""" Verify the ELF file and identify its class and endianness.
|
||||
"""
|
||||
# Note: this code reads the stream directly, without using ELFStructs,
|
||||
# since we don't yet know its exact format. ELF was designed to be
|
||||
# read like this - its e_ident field is word-size and endian agnostic.
|
||||
self.stream.seek(0)
|
||||
magic = self.stream.read(4)
|
||||
elf_assert(magic == b'\x7fELF', 'Magic number does not match')
|
||||
|
||||
ei_class = self.stream.read(1)
|
||||
if ei_class == b'\x01':
|
||||
self.elfclass = 32
|
||||
elif ei_class == b'\x02':
|
||||
self.elfclass = 64
|
||||
else:
|
||||
raise ELFError('Invalid EI_CLASS %s' % repr(ei_class))
|
||||
|
||||
ei_data = self.stream.read(1)
|
||||
if ei_data == b'\x01':
|
||||
self.little_endian = True
|
||||
elif ei_data == b'\x02':
|
||||
self.little_endian = False
|
||||
else:
|
||||
raise ELFError('Invalid EI_DATA %s' % repr(ei_data))
|
||||
|
||||
def _section_offset(self, n: int) -> int:
|
||||
""" Compute the offset of section #n in the file
|
||||
"""
|
||||
shentsize = self['e_shentsize']
|
||||
if self['e_shoff'] > 0 and shentsize < self.structs.Elf_Shdr.sizeof():
|
||||
raise ELFError('Too small e_shentsize: %s' % shentsize)
|
||||
return self['e_shoff'] + n * shentsize
|
||||
|
||||
def _segment_offset(self, n: int) -> int:
|
||||
""" Compute the offset of segment #n in the file
|
||||
"""
|
||||
phentsize = self['e_phentsize']
|
||||
if self['e_phoff'] > 0 and phentsize < self.structs.Elf_Phdr.sizeof():
|
||||
raise ELFError('Too small e_phentsize: %s' % phentsize)
|
||||
return self['e_phoff'] + n * phentsize
|
||||
|
||||
def _make_segment(self, segment_header: Container) -> Segment:
|
||||
""" Create a Segment object of the appropriate type
|
||||
"""
|
||||
segtype = segment_header['p_type']
|
||||
if segtype == 'PT_INTERP':
|
||||
return InterpSegment(segment_header, self.stream)
|
||||
elif segtype == 'PT_DYNAMIC':
|
||||
return DynamicSegment(segment_header, self.stream, self)
|
||||
elif segtype == 'PT_NOTE':
|
||||
return NoteSegment(segment_header, self.stream, self)
|
||||
else:
|
||||
return Segment(segment_header, self.stream)
|
||||
|
||||
def _get_section_header(self, n: int) -> Container:
|
||||
""" Find the header of section #n, parse it and return the struct
|
||||
"""
|
||||
|
||||
stream_pos = self._section_offset(n)
|
||||
if stream_pos > self.stream_len:
|
||||
msg = f"Reading section {n} at offset {stream_pos} past EOF {self.stream_len}"
|
||||
raise ELFParseError(msg)
|
||||
|
||||
return struct_parse(
|
||||
self.structs.Elf_Shdr,
|
||||
self.stream,
|
||||
stream_pos=stream_pos)
|
||||
|
||||
def _get_section_name(self, section_header: Container) -> str:
|
||||
""" Given a section header, find this section's name in the file's
|
||||
string table
|
||||
"""
|
||||
name_offset = section_header['sh_name']
|
||||
return self._section_header_stringtable.get_string(name_offset)
|
||||
|
||||
def _make_section(self, section_header: Container) -> Section:
|
||||
""" Create a section object of the appropriate type
|
||||
"""
|
||||
name = self._get_section_name(section_header)
|
||||
sectype = section_header['sh_type']
|
||||
|
||||
if sectype == 'SHT_STRTAB':
|
||||
return StringTableSection(section_header, name, self)
|
||||
elif sectype == 'SHT_NULL':
|
||||
return NullSection(section_header, name, self)
|
||||
elif sectype in ('SHT_SYMTAB', 'SHT_DYNSYM', 'SHT_SUNW_LDYNSYM'):
|
||||
return self._make_symbol_table_section(section_header, name)
|
||||
elif sectype == 'SHT_SYMTAB_SHNDX':
|
||||
return self._make_symbol_table_index_section(section_header, name)
|
||||
elif sectype == 'SHT_SUNW_syminfo':
|
||||
return self._make_sunwsyminfo_table_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_verneed':
|
||||
return self._make_gnu_verneed_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_verdef':
|
||||
return self._make_gnu_verdef_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_versym':
|
||||
return self._make_gnu_versym_section(section_header, name)
|
||||
elif sectype in ('SHT_REL', 'SHT_RELA'):
|
||||
return RelocationSection(section_header, name, self)
|
||||
elif sectype == 'SHT_DYNAMIC':
|
||||
return DynamicSection(section_header, name, self)
|
||||
elif sectype == 'SHT_NOTE':
|
||||
return NoteSection(section_header, name, self)
|
||||
elif sectype == 'SHT_PROGBITS' and name == '.stab':
|
||||
return StabSection(section_header, name, self)
|
||||
elif sectype == 'SHT_ARM_ATTRIBUTES':
|
||||
return ARMAttributesSection(section_header, name, self)
|
||||
elif sectype == 'SHT_RISCV_ATTRIBUTES':
|
||||
return RISCVAttributesSection(section_header, name, self)
|
||||
elif sectype == 'SHT_HASH':
|
||||
return self._make_elf_hash_section(section_header, name)
|
||||
elif sectype == 'SHT_GNU_HASH':
|
||||
return self._make_gnu_hash_section(section_header, name)
|
||||
elif sectype == 'SHT_RELR':
|
||||
return RelrRelocationSection(section_header, name, self)
|
||||
else:
|
||||
return Section(section_header, name, self)
|
||||
|
||||
@cached_property
|
||||
def _section_name_map(self) -> dict[str, int]:
|
||||
return {
|
||||
sec.name: i
|
||||
for i, sec in enumerate(self.iter_sections())
|
||||
}
|
||||
|
||||
def _make_symbol_table_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SymbolTableSection:
|
||||
""" Create a SymbolTableSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return SymbolTableSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_symbol_table_index_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SymbolTableIndexSection:
|
||||
""" Create a SymbolTableIndexSection object
|
||||
"""
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
return SymbolTableIndexSection(
|
||||
section_header, name, elffile=self,
|
||||
symboltable=linked_symtab_index)
|
||||
|
||||
def _make_sunwsyminfo_table_section(
|
||||
self,
|
||||
section_header: Container,
|
||||
name: str,
|
||||
) -> SUNWSyminfoTableSection:
|
||||
""" Create a SUNWSyminfoTableSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_symtab_section(linked_strtab_index)
|
||||
return SUNWSyminfoTableSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
symboltable=strtab_section)
|
||||
|
||||
def _make_gnu_verneed_section(self, section_header: Container, name: str) -> GNUVerNeedSection:
|
||||
""" Create a GNUVerNeedSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return GNUVerNeedSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_gnu_verdef_section(self, section_header: Container, name: str) -> GNUVerDefSection:
|
||||
""" Create a GNUVerDefSection
|
||||
"""
|
||||
linked_strtab_index = section_header['sh_link']
|
||||
strtab_section = self._get_linked_strtab_section(linked_strtab_index)
|
||||
return GNUVerDefSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
stringtable=strtab_section)
|
||||
|
||||
def _make_gnu_versym_section(self, section_header: Container, name: str) -> GNUVerSymSection:
|
||||
""" Create a GNUVerSymSection
|
||||
"""
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return GNUVerSymSection(
|
||||
section_header, name,
|
||||
elffile=self,
|
||||
symboltable=symtab_section)
|
||||
|
||||
def _make_elf_hash_section(self, section_header: Container, name: str) -> ELFHashSection:
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return ELFHashSection(
|
||||
section_header, name, self, symtab_section
|
||||
)
|
||||
|
||||
def _make_gnu_hash_section(self, section_header: Container, name: str) -> GNUHashSection:
|
||||
linked_symtab_index = section_header['sh_link']
|
||||
symtab_section = self._get_linked_symtab_section(linked_symtab_index)
|
||||
return GNUHashSection(
|
||||
section_header, name, self, symtab_section
|
||||
)
|
||||
|
||||
def _get_segment_header(self, n: int) -> Container: # Elf_Phdr:
|
||||
""" Find the header of segment #n, parse it and return the struct
|
||||
"""
|
||||
return struct_parse(
|
||||
self.structs.Elf_Phdr,
|
||||
self.stream,
|
||||
stream_pos=self._segment_offset(n))
|
||||
|
||||
@cached_property
|
||||
def _section_header_stringtable(self) -> StringTableSection:
|
||||
""" Get the string table section corresponding to the section header
|
||||
table.
|
||||
"""
|
||||
stringtable_section_num = self.get_shstrndx()
|
||||
|
||||
try:
|
||||
stringtable_section_header = self._get_section_header(stringtable_section_num)
|
||||
except ELFParseError as ex:
|
||||
raise ELFParseError("String Table not found") from ex
|
||||
|
||||
return StringTableSection(
|
||||
header=stringtable_section_header,
|
||||
name='',
|
||||
elffile=self)
|
||||
|
||||
def _parse_elf_header(self) -> Container:
|
||||
""" Parses the ELF file header and assigns the result to attributes
|
||||
of this object.
|
||||
"""
|
||||
return struct_parse(self.structs.Elf_Ehdr, self.stream, stream_pos=0)
|
||||
|
||||
def _read_dwarf_section(
|
||||
self,
|
||||
section: Section,
|
||||
relocate_dwarf_sections: bool,
|
||||
) -> DebugSectionDescriptor:
|
||||
""" Read the contents of a DWARF section from the stream and return a
|
||||
DebugSectionDescriptor. Apply relocations if asked to.
|
||||
"""
|
||||
phantom_bytes = self.has_phantom_bytes()
|
||||
# The section data is read into a new stream, for processing
|
||||
section_stream = BytesIO()
|
||||
section_data = section.data()
|
||||
section_stream.write(section_data[::2] if phantom_bytes else section_data)
|
||||
|
||||
if relocate_dwarf_sections:
|
||||
reloc_handler = RelocationHandler(self)
|
||||
reloc_section = reloc_handler.find_relocations_for_section(section)
|
||||
if reloc_section is not None:
|
||||
if phantom_bytes:
|
||||
# No guidance how should the relocation work - before or after the odd byte skip
|
||||
raise ELFParseError("This binary has relocations in the DWARF sections, currently not supported.")
|
||||
else:
|
||||
reloc_handler.apply_section_relocations(
|
||||
section_stream, reloc_section)
|
||||
|
||||
return DebugSectionDescriptor(
|
||||
stream=section_stream,
|
||||
name=section.name,
|
||||
global_offset=section['sh_offset'],
|
||||
size=section.data_size//2 if phantom_bytes else section.data_size,
|
||||
address=section['sh_addr'])
|
||||
|
||||
@staticmethod
|
||||
def _decompress_dwarf_section(section: DebugSectionDescriptor) -> DebugSectionDescriptor:
|
||||
""" Returns the uncompressed contents of the provided DWARF section.
|
||||
"""
|
||||
# TODO: support other compression formats from readelf.c
|
||||
assert section.size > 12, 'Unsupported compression format.'
|
||||
|
||||
section.stream.seek(0)
|
||||
# According to readelf.c the content should contain "ZLIB"
|
||||
# followed by the uncompressed section size - 8 bytes in
|
||||
# big-endian order
|
||||
compression_type = section.stream.read(4)
|
||||
assert compression_type == b'ZLIB', \
|
||||
'Invalid compression type: %r' % (compression_type)
|
||||
|
||||
uncompressed_size = struct.unpack('>Q', section.stream.read(8))[0]
|
||||
|
||||
decompressor = zlib.decompressobj()
|
||||
uncompressed_stream = BytesIO()
|
||||
while True:
|
||||
chunk = section.stream.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
uncompressed_stream.write(decompressor.decompress(chunk))
|
||||
uncompressed_stream.write(decompressor.flush())
|
||||
|
||||
uncompressed_stream.seek(0, io.SEEK_END)
|
||||
size = uncompressed_stream.tell()
|
||||
assert uncompressed_size == size, \
|
||||
'Wrong uncompressed size: expected %r, but got %r' % (
|
||||
uncompressed_size, size,
|
||||
)
|
||||
|
||||
return section._replace(stream=uncompressed_stream, size=size)
|
||||
|
||||
def close(self) -> None:
|
||||
self.stream.close()
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
type: type[BaseException] | None,
|
||||
value: BaseException | None,
|
||||
traceback: TracebackType | None,
|
||||
) -> None:
|
||||
self.close()
|
||||
|
||||
def has_phantom_bytes(self) -> bool:
|
||||
"""The XC16 compiler for the PIC microcontrollers emits DWARF where all odd bytes in all DWARF sections
|
||||
are to be discarded ("phantom").
|
||||
|
||||
We don't know where does the phantom byte discarding fit into the usual chain of section content transforms.
|
||||
There are no XC16/PIC binaries in the corpus with relocations against DWARF, and the DWARF section compression
|
||||
seems to be unsupported by XC16.
|
||||
"""
|
||||
# Vendor flag EF_PIC30_NO_PHANTOM_BYTE=0x80000000: clear means phantom bytes are present
|
||||
return self['e_machine'] == 'EM_DSPIC30F' and (self['e_flags'] & 0x80000000) == 0
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,266 @@
|
||||
#------------------------------------------------------------------------------
|
||||
# elftools: elf/gnuversions.py
|
||||
#
|
||||
# ELF sections
|
||||
#
|
||||
# Yann Rouillard (yann@pleiades.fr.eu.org)
|
||||
# This code is in the public domain
|
||||
#------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ..common.utils import struct_parse, elf_assert
|
||||
from .sections import Section, Symbol
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.core import Struct
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
from .sections import StringTableSection, SymbolTableSection
|
||||
|
||||
|
||||
class Version:
|
||||
""" Version object - representing a version definition or dependency
|
||||
entry from a "Version Needed" or a "Version Dependency" table section.
|
||||
|
||||
This kind of entry contains a pointer to an array of auxiliary entries
|
||||
that store the information about version names or dependencies.
|
||||
These entries are not stored in this object and should be accessed
|
||||
through the appropriate method of a section object which will return
|
||||
an iterator of VersionAuxiliary objects.
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to
|
||||
verdef/verneed entry
|
||||
"""
|
||||
def __init__(self, entry: Container, name: str | None = None) -> None:
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entry
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class VersionAuxiliary:
|
||||
""" Version Auxiliary object - representing an auxiliary entry of a version
|
||||
definition or dependency entry
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to the
|
||||
verdaux/vernaux entry
|
||||
"""
|
||||
def __init__(self, entry: Container, name: str) -> None:
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class GNUVersionSection(Section):
|
||||
""" Common ancestor class for ELF SUNW|GNU Version Needed/Dependency
|
||||
sections class which contains shareable code
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
field_prefix: str,
|
||||
version_struct: Struct,
|
||||
version_auxiliaries_struct: Struct,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.stringtable = stringtable
|
||||
self.field_prefix = field_prefix
|
||||
self.version_struct = version_struct
|
||||
self.version_auxiliaries_struct = version_auxiliaries_struct
|
||||
|
||||
def num_versions(self) -> int:
|
||||
""" Number of version entries in the section
|
||||
"""
|
||||
return self['sh_info']
|
||||
|
||||
def _field_name(self, name: str, auxiliary: bool = False) -> str:
|
||||
""" Return the real field's name of version or a version auxiliary
|
||||
entry
|
||||
"""
|
||||
middle = 'a_' if auxiliary else '_'
|
||||
return self.field_prefix + middle + name
|
||||
|
||||
def _iter_version_auxiliaries(
|
||||
self,
|
||||
entry_offset: int,
|
||||
count: int,
|
||||
) -> Iterator[VersionAuxiliary]:
|
||||
""" Yield all auxiliary entries of a version entry
|
||||
"""
|
||||
name_field = self._field_name('name', auxiliary=True)
|
||||
next_field = self._field_name('next', auxiliary=True)
|
||||
|
||||
for _ in range(count):
|
||||
entry = struct_parse(
|
||||
self.version_auxiliaries_struct,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
|
||||
name = self.stringtable.get_string(entry[name_field])
|
||||
version_aux = VersionAuxiliary(entry, name)
|
||||
yield version_aux
|
||||
|
||||
entry_offset += entry[next_field]
|
||||
|
||||
def iter_versions(self) -> Iterator[tuple[Version, Iterator[VersionAuxiliary]]]:
|
||||
""" Yield all the version entries in the section
|
||||
Each time it returns the main version structure
|
||||
and an iterator to walk through its auxiliaries entries
|
||||
"""
|
||||
aux_field = self._field_name('aux')
|
||||
count_field = self._field_name('cnt')
|
||||
next_field = self._field_name('next')
|
||||
|
||||
entry_offset = self['sh_offset']
|
||||
for _ in range(self.num_versions()):
|
||||
entry = struct_parse(
|
||||
self.version_struct,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
|
||||
elf_assert(entry[count_field] > 0,
|
||||
'Expected number of version auxiliary entries (%s) to be > 0'
|
||||
'for the following version entry: %s' % (
|
||||
count_field, str(entry)))
|
||||
|
||||
version = Version(entry)
|
||||
aux_entries_offset = entry_offset + entry[aux_field]
|
||||
version_auxiliaries_iter = self._iter_version_auxiliaries(
|
||||
aux_entries_offset, entry[count_field])
|
||||
|
||||
yield version, version_auxiliaries_iter
|
||||
|
||||
entry_offset += entry[next_field]
|
||||
|
||||
|
||||
class GNUVerNeedSection(GNUVersionSection):
|
||||
""" ELF SUNW or GNU Version Needed table section.
|
||||
Has an associated StringTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
header, name, elffile, stringtable, 'vn',
|
||||
elffile.structs.Elf_Verneed, elffile.structs.Elf_Vernaux)
|
||||
|
||||
def has_indexes(self) -> bool:
|
||||
""" Return True if at least one version definition entry has an index
|
||||
that is stored in the vna_other field.
|
||||
This information is used for symbol versioning
|
||||
"""
|
||||
return self._has_indexes
|
||||
|
||||
@cached_property
|
||||
def _has_indexes(self) -> bool:
|
||||
return any(
|
||||
vernaux['vna_other']
|
||||
for _, vernaux_iter in self.iter_versions()
|
||||
for vernaux in vernaux_iter
|
||||
)
|
||||
|
||||
def iter_versions(self) -> Iterator[tuple[Version, Iterator[VersionAuxiliary]]]:
|
||||
for verneed, vernaux in super().iter_versions():
|
||||
verneed.name = self.stringtable.get_string(verneed['vn_file'])
|
||||
yield verneed, vernaux
|
||||
|
||||
def get_version(self, index: int) -> tuple[Version, VersionAuxiliary] | None:
|
||||
""" Get the version information located at index #n in the table
|
||||
Return boths the verneed structure and the vernaux structure
|
||||
that contains the name of the version
|
||||
"""
|
||||
for verneed, vernaux_iter in self.iter_versions():
|
||||
for vernaux in vernaux_iter:
|
||||
if vernaux['vna_other'] == index:
|
||||
return verneed, vernaux
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class GNUVerDefSection(GNUVersionSection):
|
||||
""" ELF SUNW or GNU Version Definition table section.
|
||||
Has an associated StringTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
header, name, elffile, stringtable, 'vd',
|
||||
elffile.structs.Elf_Verdef, elffile.structs.Elf_Verdaux)
|
||||
|
||||
def get_version(self, index: int) -> tuple[Version, Iterator[VersionAuxiliary]] | None:
|
||||
""" Get the version information located at index #n in the table
|
||||
Return boths the verdef structure and an iterator to retrieve
|
||||
both the version names and dependencies in the form of
|
||||
verdaux entries
|
||||
"""
|
||||
for verdef, verdaux_iter in self.iter_versions():
|
||||
if verdef['vd_ndx'] == index:
|
||||
return verdef, verdaux_iter
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class GNUVerSymSection(Section):
|
||||
""" ELF SUNW or GNU Versym table section.
|
||||
Has an associated SymbolTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: SymbolTableSection,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize']
|
||||
|
||||
def get_symbol(self, n: int) -> Symbol:
|
||||
""" Get the symbol at index #n from the table (Symbol object)
|
||||
It begins at 1 and not 0 since the first entry is used to
|
||||
store the current version of the syminfo table
|
||||
"""
|
||||
# Grab the symbol's entry from the stream
|
||||
entry_offset = self['sh_offset'] + n * self['sh_entsize']
|
||||
entry = struct_parse(
|
||||
self.structs.Elf_Versym,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
# Find the symbol name in the associated symbol table
|
||||
name = self.symboltable.get_symbol(n).name
|
||||
return Symbol(entry, name)
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
@@ -0,0 +1,238 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/hash.py
|
||||
#
|
||||
# ELF hash table sections
|
||||
#
|
||||
# Andreas Ziegler (andreas.ziegler@fau.de)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
|
||||
from ..common.utils import struct_parse
|
||||
from ..construct.lib.container import Container
|
||||
from .sections import Section
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .elffile import ELFFile
|
||||
from .sections import Symbol
|
||||
|
||||
|
||||
class _SymbolTable(Protocol):
|
||||
"""Common base-class of elftools.elf.sections.SymbolTableSection and
|
||||
elftools.elf.dynamic.DynamicSegment to be consumed by
|
||||
(ELF|GNU)Hash(Section|Table)."""
|
||||
def get_symbol(self, index: int, /) -> Symbol | None: ...
|
||||
|
||||
|
||||
class ELFHashTable:
|
||||
""" Representation of an ELF hash table to find symbols in the
|
||||
symbol table - useful for super-stripped binaries without section
|
||||
headers where only the start of the symbol table is known from the
|
||||
dynamic segment. The layout and contents are nicely described at
|
||||
https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/.
|
||||
|
||||
The symboltable argument needs to implement a get_symbol() method -
|
||||
in a regular ELF file, this will be the linked symbol table section
|
||||
as indicated by the sh_link attribute. For super-stripped binaries,
|
||||
one should use the DynamicSegment object as the symboltable as it
|
||||
supports symbol lookup without access to a symbol table section.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
elffile: ELFFile,
|
||||
start_offset: int,
|
||||
size: int | None,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
"""
|
||||
Args:
|
||||
elffile (ELFFile): The ELF file.
|
||||
start_offset: The offset of the start of the symbol table in the ELF file.
|
||||
size: Size of the table in bytes (can be None if unknown).
|
||||
symboltable: A symbol table with a get_symbol() method to do symbol lookup.
|
||||
"""
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
if size == 0: # size may also be None if its unknown
|
||||
self.params = Container(**{
|
||||
'nbuckets': 0,
|
||||
'nchains': 0,
|
||||
'buckets': [],
|
||||
'chains': [],
|
||||
})
|
||||
else:
|
||||
self.params = struct_parse(self.elffile.structs.Elf_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
|
||||
def get_number_of_symbols(self) -> int:
|
||||
""" Get the number of symbols from the hash table parameters.
|
||||
"""
|
||||
return self.params['nchains']
|
||||
|
||||
def get_symbol(self, name: str) -> Symbol | None:
|
||||
""" Look up a symbol from this hash table with the given name.
|
||||
"""
|
||||
if self.params['nbuckets'] == 0:
|
||||
return None
|
||||
hval = self.elf_hash(name) % self.params['nbuckets']
|
||||
symndx = self.params['buckets'][hval]
|
||||
while symndx != 0:
|
||||
sym = self._symboltable.get_symbol(symndx)
|
||||
if sym and sym.name == name:
|
||||
return sym
|
||||
symndx = self.params['chains'][symndx]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def elf_hash(name: bytes | str) -> int:
|
||||
""" Compute the hash value for a given symbol name.
|
||||
"""
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode('utf-8')
|
||||
h = 0
|
||||
x = 0
|
||||
for c in bytearray(name):
|
||||
h = (h << 4) + c
|
||||
x = h & 0xF0000000
|
||||
if x != 0:
|
||||
h ^= (x >> 24)
|
||||
h &= ~x
|
||||
return h
|
||||
|
||||
|
||||
class ELFHashSection(Section, ELFHashTable):
|
||||
""" Section representation of an ELF hash table. In regular ELF files, this
|
||||
allows us to use the common functions defined on Section objects when
|
||||
dealing with the hash table.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
ELFHashTable.__init__(self, elffile, self['sh_offset'], self['sh_size'], symboltable)
|
||||
|
||||
|
||||
class GNUHashTable:
|
||||
""" Representation of a GNU hash table to find symbols in the
|
||||
symbol table - useful for super-stripped binaries without section
|
||||
headers where only the start of the symbol table is known from the
|
||||
dynamic segment. The layout and contents are nicely described at
|
||||
https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/.
|
||||
|
||||
The symboltable argument needs to implement a get_symbol() method -
|
||||
in a regular ELF file, this will be the linked symbol table section
|
||||
as indicated by the sh_link attribute. For super-stripped binaries,
|
||||
one should use the DynamicSegment object as the symboltable as it
|
||||
supports symbol lookup without access to a symbol table section.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
elffile: ELFFile,
|
||||
start_offset: int,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
self.elffile = elffile
|
||||
self._symboltable = symboltable
|
||||
self.params: Container = struct_parse(self.elffile.structs.Gnu_Hash,
|
||||
self.elffile.stream,
|
||||
start_offset)
|
||||
# Element sizes in the hash table
|
||||
self._wordsize: int = self.elffile.structs.Elf_word('').sizeof()
|
||||
self._xwordsize: int = self.elffile.structs.Elf_xword('').sizeof()
|
||||
self._chain_pos: int = start_offset + 4 * self._wordsize + \
|
||||
self.params['bloom_size'] * self._xwordsize + \
|
||||
self.params['nbuckets'] * self._wordsize
|
||||
|
||||
def get_number_of_symbols(self) -> int:
|
||||
""" Get the number of symbols in the hash table by finding the bucket
|
||||
with the highest symbol index and walking to the end of its chain.
|
||||
"""
|
||||
# Find highest index in buckets array
|
||||
max_idx = max(self.params['buckets'])
|
||||
if max_idx < self.params['symoffset']:
|
||||
return self.params['symoffset']
|
||||
|
||||
# Position the stream at the start of the corresponding chain
|
||||
max_chain_pos = self._chain_pos + \
|
||||
(max_idx - self.params['symoffset']) * self._wordsize
|
||||
self.elffile.stream.seek(max_chain_pos)
|
||||
hash_format = '<I' if self.elffile.little_endian else '>I'
|
||||
|
||||
# Walk the chain to its end (lowest bit is set)
|
||||
while True:
|
||||
cur_hash = struct.unpack(hash_format, self.elffile.stream.read(self._wordsize))[0]
|
||||
if cur_hash & 1:
|
||||
return max_idx + 1
|
||||
|
||||
max_idx += 1
|
||||
|
||||
def _matches_bloom(self, H1: int) -> bool:
|
||||
""" Helper function to check if the given hash could be in the hash
|
||||
table by testing it against the bloom filter.
|
||||
"""
|
||||
arch_bits = self.elffile.elfclass
|
||||
H2 = H1 >> self.params['bloom_shift']
|
||||
word_idx = int(H1 / arch_bits) % self.params['bloom_size']
|
||||
BITMASK = (1 << (H1 % arch_bits)) | (1 << (H2 % arch_bits))
|
||||
return (self.params['bloom'][word_idx] & BITMASK) == BITMASK
|
||||
|
||||
def get_symbol(self, name: str) -> Symbol | None:
|
||||
""" Look up a symbol from this hash table with the given name.
|
||||
"""
|
||||
namehash = self.gnu_hash(name)
|
||||
if not self._matches_bloom(namehash):
|
||||
return None
|
||||
|
||||
symidx = self.params['buckets'][namehash % self.params['nbuckets']]
|
||||
if symidx < self.params['symoffset']:
|
||||
return None
|
||||
|
||||
self.elffile.stream.seek(self._chain_pos + (symidx - self.params['symoffset']) * self._wordsize)
|
||||
hash_format = '<I' if self.elffile.little_endian else '>I'
|
||||
while True:
|
||||
cur_hash = struct.unpack(hash_format, self.elffile.stream.read(self._wordsize))[0]
|
||||
if cur_hash | 1 == namehash | 1:
|
||||
symbol = self._symboltable.get_symbol(symidx)
|
||||
if symbol and name == symbol.name:
|
||||
return symbol
|
||||
|
||||
if cur_hash & 1:
|
||||
break
|
||||
symidx += 1
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def gnu_hash(key: bytes | str) -> int:
|
||||
""" Compute the GNU-style hash value for a given symbol name.
|
||||
"""
|
||||
if not isinstance(key, bytes):
|
||||
key = key.encode('utf-8')
|
||||
h = 5381
|
||||
for c in bytearray(key):
|
||||
h = h * 33 + c
|
||||
return h & 0xFFFFFFFF
|
||||
|
||||
|
||||
class GNUHashSection(Section, GNUHashTable):
|
||||
""" Section representation of a GNU hash table. In regular ELF files, this
|
||||
allows us to use the common functions defined on Section objects when
|
||||
dealing with the hash table.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: _SymbolTable,
|
||||
) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
GNUHashTable.__init__(self, elffile, self['sh_offset'], symboltable)
|
||||
@@ -0,0 +1,80 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/notes.py
|
||||
#
|
||||
# ELF notes
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..common.utils import struct_parse, roundup, bytes2str
|
||||
from ..construct import CString
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct.lib.container import Container
|
||||
from .elffile import ELFFile
|
||||
|
||||
|
||||
def iter_notes(elffile: ELFFile, offset: int, size: int) -> Iterator[Container]:
|
||||
""" Yield all the notes in a section or segment.
|
||||
"""
|
||||
end = offset + size
|
||||
nhdr_size = elffile.structs.Elf_Nhdr.sizeof()
|
||||
# Note: a note's name and data are 4-byte aligned, but it's possible there's
|
||||
# additional padding at the end to satisfy the alignment requirement of the segment.
|
||||
while offset + nhdr_size < end:
|
||||
note: Container = struct_parse(
|
||||
elffile.structs.Elf_Nhdr,
|
||||
elffile.stream,
|
||||
stream_pos=offset)
|
||||
note['n_offset'] = offset
|
||||
offset += nhdr_size
|
||||
elffile.stream.seek(offset)
|
||||
if note['n_namesz']:
|
||||
# n_namesz is 4-byte aligned.
|
||||
disk_namesz: int = roundup(note['n_namesz'], 2)
|
||||
note['n_name'] = bytes2str(
|
||||
CString('').parse(elffile.stream.read(disk_namesz)))
|
||||
offset += disk_namesz
|
||||
else:
|
||||
note['n_name'] = None
|
||||
|
||||
desc_data: bytes = elffile.stream.read(note['n_descsz'])
|
||||
note['n_descdata'] = desc_data
|
||||
if note['n_type'] == 'NT_GNU_ABI_TAG' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_abi,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_GNU_BUILD_ID' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = bytes(desc_data).hex()
|
||||
elif note['n_type'] == 'NT_GNU_GOLD_VERSION' and note['n_name'] == 'GNU':
|
||||
note['n_desc'] = bytes2str(desc_data)
|
||||
elif note['n_type'] == 'NT_PRPSINFO':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_Prpsinfo,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_FILE':
|
||||
note['n_desc'] = struct_parse(elffile.structs.Elf_Nt_File,
|
||||
elffile.stream,
|
||||
offset)
|
||||
elif note['n_type'] == 'NT_GNU_PROPERTY_TYPE_0' and note['n_name'] == 'GNU':
|
||||
off = offset
|
||||
props: list[Container] = []
|
||||
# n_descsz contains the size of the note "descriptor" (the data payload),
|
||||
# excluding padding. See "Note Section" in https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
current_note_end: int = offset + note['n_descsz']
|
||||
while off < current_note_end:
|
||||
p: Container = struct_parse(elffile.structs.Elf_Prop, elffile.stream, off)
|
||||
off += roundup(p.pr_datasz + 8, 2 if elffile.elfclass == 32 else 3)
|
||||
props.append(p)
|
||||
note['n_desc'] = props
|
||||
else:
|
||||
note['n_desc'] = desc_data
|
||||
offset += roundup(note['n_descsz'], 2)
|
||||
note['n_size'] = offset - note['n_offset']
|
||||
yield note
|
||||
@@ -0,0 +1,543 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/relocation.py
|
||||
#
|
||||
# ELF relocations
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple, Protocol
|
||||
|
||||
from ..common.exceptions import ELFRelocationError
|
||||
from ..common.utils import elf_assert, struct_parse
|
||||
from .sections import Section, SymbolTableSection
|
||||
from .enums import (
|
||||
ENUM_RELOC_TYPE_i386, ENUM_RELOC_TYPE_x64, ENUM_RELOC_TYPE_MIPS,
|
||||
ENUM_RELOC_TYPE_ARM, ENUM_RELOC_TYPE_AARCH64, ENUM_RELOC_TYPE_PPC64,
|
||||
ENUM_RELOC_TYPE_S390X, ENUM_RELOC_TYPE_BPF, ENUM_RELOC_TYPE_LOONGARCH)
|
||||
from ..construct import Container
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator, Mapping
|
||||
|
||||
from .elffile import ELFFile
|
||||
|
||||
|
||||
class Relocation:
|
||||
""" Relocation object - representing a single relocation entry. Allows
|
||||
dictionary-like access to the entry's fields.
|
||||
|
||||
Can be either a REL or RELA relocation.
|
||||
"""
|
||||
def __init__(self, entry: Container, elffile: ELFFile) -> None:
|
||||
self.entry = entry
|
||||
self.elffile = elffile
|
||||
|
||||
def is_RELA(self) -> bool:
|
||||
""" Is this a RELA relocation? If not, it's REL.
|
||||
"""
|
||||
return 'r_addend' in self.entry
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '<Relocation (%s): %s>' % (
|
||||
'RELA' if self.is_RELA() else 'REL',
|
||||
self.entry)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class RelocationTable:
|
||||
""" Shared functionality between relocation sections and relocation tables
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
elffile: ELFFile,
|
||||
offset: int,
|
||||
size: int,
|
||||
is_rela: bool,
|
||||
) -> None:
|
||||
self._stream = elffile.stream
|
||||
self._elffile = elffile
|
||||
self._elfstructs = elffile.structs
|
||||
self._size = size
|
||||
self._offset = offset
|
||||
self._is_rela = is_rela
|
||||
|
||||
if is_rela:
|
||||
self.entry_struct = self._elfstructs.Elf_Rela
|
||||
else:
|
||||
self.entry_struct = self._elfstructs.Elf_Rel
|
||||
|
||||
self.entry_size = self.entry_struct.sizeof()
|
||||
|
||||
def is_RELA(self) -> bool:
|
||||
""" Is this a RELA relocation section? If not, it's REL.
|
||||
"""
|
||||
return self._is_rela
|
||||
|
||||
def num_relocations(self) -> int:
|
||||
""" Number of relocations in the section
|
||||
"""
|
||||
return self._size // self.entry_size
|
||||
|
||||
def get_relocation(self, n: int) -> Relocation:
|
||||
""" Get the relocation at index #n from the section (Relocation object)
|
||||
"""
|
||||
entry_offset = self._offset + n * self.entry_size
|
||||
entry = struct_parse(
|
||||
self.entry_struct,
|
||||
self._stream,
|
||||
stream_pos=entry_offset)
|
||||
return Relocation(entry, self._elffile)
|
||||
|
||||
def iter_relocations(self) -> Iterator[Relocation]:
|
||||
""" Yield all the relocations in the section
|
||||
"""
|
||||
for i in range(self.num_relocations()):
|
||||
yield self.get_relocation(i)
|
||||
|
||||
|
||||
class RelocationSection(Section, RelocationTable):
|
||||
""" ELF relocation section. Serves as a collection of Relocation entries.
|
||||
"""
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
RelocationTable.__init__(self, self.elffile,
|
||||
self['sh_offset'], self['sh_size'], header['sh_type'] == 'SHT_RELA')
|
||||
|
||||
elf_assert(header['sh_type'] in ('SHT_REL', 'SHT_RELA'),
|
||||
'Unknown relocation type section')
|
||||
elf_assert(header['sh_entsize'] == self.entry_size,
|
||||
'Expected sh_entsize of %s section to be %s' % (
|
||||
header['sh_type'], self.entry_size))
|
||||
|
||||
|
||||
class RelrRelocationTable:
|
||||
""" RELR compressed relocation table. This stores relative relocations
|
||||
in a compressed format. An entry with an even value serves as an
|
||||
'anchor' that defines a base address. Following this entry are one or
|
||||
more bitmaps for consecutive addresses after the anchor which determine
|
||||
if the corresponding relocation exists (if the bit is 1) or if it is
|
||||
skipped. Addends are stored at the respective addresses (as in REL
|
||||
relocations).
|
||||
"""
|
||||
|
||||
def __init__(self, elffile: ELFFile, offset: int, size: int, entrysize: int) -> None:
|
||||
self._elffile = elffile
|
||||
self._offset = offset
|
||||
self._size = size
|
||||
self._relr_struct = self._elffile.structs.Elf_Relr
|
||||
self._entrysize = self._relr_struct.sizeof()
|
||||
|
||||
elf_assert(self._entrysize == entrysize,
|
||||
'Expected RELR entry size to be %s, got %s' % (
|
||||
self._entrysize, entrysize))
|
||||
|
||||
def iter_relocations(self) -> Iterator[Relocation]:
|
||||
""" Yield all the relocations in the section
|
||||
"""
|
||||
|
||||
# If DT_RELRSZ is zero, offset is meaningless and could be None.
|
||||
if self._size == 0:
|
||||
return
|
||||
|
||||
limit = self._offset + self._size
|
||||
relr = self._offset
|
||||
# The addresses of relocations in a bitmap are calculated from a base
|
||||
# value provided in an initial 'anchor' relocation.
|
||||
base: int | None = None
|
||||
while relr < limit:
|
||||
entry = struct_parse(self._relr_struct,
|
||||
self._elffile.stream,
|
||||
stream_pos=relr)
|
||||
entry_offset: int = entry['r_offset']
|
||||
if (entry_offset & 1) == 0:
|
||||
# We found an anchor, take the current value as the base address
|
||||
# for the following bitmaps and move the 'where' pointer to the
|
||||
# beginning of the first bitmap.
|
||||
base = entry_offset
|
||||
base += self._entrysize
|
||||
yield Relocation(entry, self._elffile)
|
||||
else:
|
||||
# We're processing a bitmap.
|
||||
elf_assert(base is not None, 'RELR bitmap without base address')
|
||||
assert base is not None
|
||||
i = 0
|
||||
while True:
|
||||
# Iterate over all bits except the least significant one.
|
||||
entry_offset = (entry_offset >> 1)
|
||||
if entry_offset == 0:
|
||||
break
|
||||
# if the current LSB is set, we have a relocation at the
|
||||
# corresponding address so generate a Relocation with the
|
||||
# matching offset
|
||||
if (entry_offset & 1) != 0:
|
||||
calc_offset = base + i * self._entrysize
|
||||
yield Relocation(Container(r_offset = calc_offset),
|
||||
self._elffile)
|
||||
i += 1
|
||||
# Advance 'base' past the current bitmap (8 == CHAR_BIT). There
|
||||
# are 63 (or 31 for 32-bit ELFs) entries in each bitmap, and
|
||||
# every bit corresponds to an ELF_addr-sized relocation.
|
||||
base += (8 * self._entrysize - 1) * self._elffile.structs.Elf_addr('').sizeof()
|
||||
# Advance to the next entry
|
||||
relr += self._entrysize
|
||||
|
||||
def num_relocations(self) -> int:
|
||||
""" Number of relocations in the section
|
||||
"""
|
||||
return len(self._cached_relocations)
|
||||
|
||||
def get_relocation(self, n: int) -> Relocation:
|
||||
""" Get the relocation at index #n from the section (Relocation object)
|
||||
"""
|
||||
return self._cached_relocations[n]
|
||||
|
||||
@cached_property
|
||||
def _cached_relocations(self) -> list[Relocation]:
|
||||
return list(self.iter_relocations())
|
||||
|
||||
|
||||
class RelrRelocationSection(Section, RelrRelocationTable):
|
||||
""" ELF RELR relocation section. Serves as a collection of RELR relocation entries.
|
||||
"""
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
Section.__init__(self, header, name, elffile)
|
||||
RelrRelocationTable.__init__(self, self.elffile,
|
||||
self['sh_offset'], self['sh_size'], self['sh_entsize'])
|
||||
|
||||
|
||||
class _RelocationFunction(Protocol):
|
||||
def __call__(self, value: int, sym_value: int, offset: int, addend: int = 0) -> int: ...
|
||||
|
||||
|
||||
def _reloc_calc_identity(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return value
|
||||
|
||||
|
||||
def _reloc_calc_sym_plus_value(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return sym_value + value + addend
|
||||
|
||||
|
||||
def _reloc_calc_sym_plus_value_pcrel(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return sym_value + value - offset
|
||||
|
||||
|
||||
def _reloc_calc_sym_plus_addend(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return sym_value + addend
|
||||
|
||||
|
||||
def _reloc_calc_sym_plus_addend_pcrel(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return sym_value + addend - offset
|
||||
|
||||
|
||||
def _reloc_calc_value_minus_sym_addend(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return value - sym_value - addend
|
||||
|
||||
|
||||
def _arm_reloc_calc_sym_plus_value_pcrel(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return sym_value // 4 + value - offset // 4
|
||||
|
||||
|
||||
def _bpf_64_32_reloc_calc_sym_plus_addend(value: int, sym_value: int, offset: int, addend: int = 0) -> int:
|
||||
return (sym_value + addend) // 8 - 1
|
||||
|
||||
|
||||
class RelocationHandler:
|
||||
""" Handles the logic of relocations in ELF files.
|
||||
"""
|
||||
def __init__(self, elffile: ELFFile) -> None:
|
||||
self.elffile = elffile
|
||||
|
||||
def find_relocations_for_section(self, section: Section) -> RelocationSection | None:
|
||||
""" Given a section, find the relocation section for it in the ELF
|
||||
file. Return a RelocationSection object, or None if none was
|
||||
found.
|
||||
"""
|
||||
reloc_section_names = (
|
||||
'.rel' + section.name,
|
||||
'.rela' + section.name)
|
||||
# Find the relocation section aimed at this one. Currently assume
|
||||
# that either .rel or .rela section exists for this section, but
|
||||
# not both.
|
||||
for relsection in self.elffile.iter_sections():
|
||||
if ( isinstance(relsection, RelocationSection) and
|
||||
relsection.name in reloc_section_names):
|
||||
return relsection
|
||||
return None
|
||||
|
||||
def apply_section_relocations(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
reloc_section: RelocationSection,
|
||||
) -> None:
|
||||
""" Apply all relocations in reloc_section (a RelocationSection object)
|
||||
to the given stream, that contains the data of the section that is
|
||||
being relocated. The stream is modified as a result.
|
||||
"""
|
||||
# The symbol table associated with this relocation section
|
||||
symtab = self.elffile.get_section(reloc_section['sh_link'])
|
||||
assert isinstance(symtab, SymbolTableSection)
|
||||
for reloc in reloc_section.iter_relocations():
|
||||
self._do_apply_relocation(stream, reloc, symtab)
|
||||
|
||||
def _do_apply_relocation(
|
||||
self,
|
||||
stream: IO[bytes],
|
||||
reloc: Relocation,
|
||||
symtab: SymbolTableSection,
|
||||
) -> None:
|
||||
# Preparations for performing the relocation: obtain the value of
|
||||
# the symbol mentioned in the relocation, as well as the relocation
|
||||
# recipe which tells us how to actually perform it.
|
||||
# All peppered with some sanity checking.
|
||||
if reloc['r_info_sym'] >= symtab.num_symbols():
|
||||
raise ELFRelocationError(
|
||||
'Invalid symbol reference in relocation: index %s' % (
|
||||
reloc['r_info_sym']))
|
||||
sym_value = symtab.get_symbol(reloc['r_info_sym'])['st_value']
|
||||
|
||||
reloc_type = reloc['r_info_type']
|
||||
recipe = None
|
||||
|
||||
if self.elffile.get_machine_arch() == 'x86':
|
||||
if reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected RELA relocation for x86: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_X86.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'x64':
|
||||
if not reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected REL relocation for x64: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_X64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'MIPS':
|
||||
if reloc.is_RELA():
|
||||
if reloc_type == ENUM_RELOC_TYPE_MIPS['R_MIPS_64']:
|
||||
if reloc['r_type2'] != 0 or reloc['r_type3'] != 0 or reloc['r_ssym'] != 0:
|
||||
raise ELFRelocationError(
|
||||
'Multiple relocations in R_MIPS_64 are not implemented: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_MIPS_RELA.get(reloc_type, None)
|
||||
else:
|
||||
recipe = self._RELOCATION_RECIPES_MIPS_REL.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'ARM':
|
||||
if reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected RELA relocation for ARM: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_ARM.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'AArch64':
|
||||
recipe = self._RELOCATION_RECIPES_AARCH64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == '64-bit PowerPC':
|
||||
recipe = self._RELOCATION_RECIPES_PPC64.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'IBM S/390':
|
||||
recipe = self._RELOCATION_RECIPES_S390X.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'Linux BPF - in-kernel virtual machine':
|
||||
recipe = self._RELOCATION_RECIPES_EBPF.get(reloc_type, None)
|
||||
elif self.elffile.get_machine_arch() == 'LoongArch':
|
||||
if not reloc.is_RELA():
|
||||
raise ELFRelocationError(
|
||||
'Unexpected REL relocation for LoongArch: %s' % reloc)
|
||||
recipe = self._RELOCATION_RECIPES_LOONGARCH.get(reloc_type, None)
|
||||
|
||||
if recipe is None:
|
||||
raise ELFRelocationError(
|
||||
'Unsupported relocation type: %s' % reloc_type)
|
||||
|
||||
# So now we have everything we need to actually perform the relocation.
|
||||
# Let's get to it:
|
||||
|
||||
# 0. Find out which struct we're going to be using to read this value
|
||||
# from the stream and write it back.
|
||||
if recipe.bytesize == 4:
|
||||
value_struct = self.elffile.structs.Elf_word('')
|
||||
elif recipe.bytesize == 8:
|
||||
value_struct = self.elffile.structs.Elf_word64('')
|
||||
elif recipe.bytesize == 1:
|
||||
value_struct = self.elffile.structs.Elf_byte('')
|
||||
elif recipe.bytesize == 2:
|
||||
value_struct = self.elffile.structs.Elf_half('')
|
||||
else:
|
||||
raise ELFRelocationError('Invalid bytesize %s for relocation' %
|
||||
recipe.bytesize)
|
||||
|
||||
# 1. Read the value from the stream (with correct size and endianness)
|
||||
original_value = struct_parse(
|
||||
value_struct,
|
||||
stream,
|
||||
stream_pos=reloc['r_offset'])
|
||||
# 2. Apply the relocation to the value, acting according to the recipe
|
||||
relocated_value = recipe.calc_func(
|
||||
value=original_value,
|
||||
sym_value=sym_value,
|
||||
offset=reloc['r_offset'],
|
||||
addend=reloc['r_addend'] if recipe.has_addend else 0)
|
||||
# 3. Write the relocated value back into the stream
|
||||
stream.seek(reloc['r_offset'])
|
||||
|
||||
# Make sure the relocated value fits back by wrapping it around. This
|
||||
# looks like a problem, but it seems to be the way this is done in
|
||||
# binutils too.
|
||||
relocated_value = relocated_value % (2 ** (recipe.bytesize * 8))
|
||||
value_struct.build_stream(relocated_value, stream)
|
||||
|
||||
# Relocations are represented by "recipes". Each recipe specifies:
|
||||
# bytesize: The number of bytes to read (and write back) to the section.
|
||||
# This is the unit of data on which relocation is performed.
|
||||
# has_addend: Does this relocation have an extra addend?
|
||||
# calc_func: A function that performs the relocation on an extracted
|
||||
# value, and returns the updated value.
|
||||
#
|
||||
class _RELOCATION_RECIPE_TYPE(NamedTuple):
|
||||
bytesize: int
|
||||
has_addend: bool
|
||||
calc_func: _RelocationFunction
|
||||
|
||||
_RELOCATION_RECIPES_ARM: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_ARM['R_ARM_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_ARM['R_ARM_CALL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_arm_reloc_calc_sym_plus_value_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_AARCH64: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_ABS64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_AARCH64['R_AARCH64_PREL32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
}
|
||||
|
||||
# https://dmz-portal.mips.com/wiki/MIPS_relocation_types
|
||||
_RELOCATION_RECIPES_MIPS_REL: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
}
|
||||
_RELOCATION_RECIPES_MIPS_RELA: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_MIPS['R_MIPS_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_PPC64: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_ADDR32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_REL32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_PPC64['R_PPC64_ADDR64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_X86: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_i386['R_386_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_i386['R_386_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_i386['R_386_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False,
|
||||
calc_func=_reloc_calc_sym_plus_value_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_X64: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_x64['R_X86_64_32S']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
# https://www.kernel.org/doc/html/latest/bpf/llvm_reloc.html#different-relocation-types
|
||||
_RELOCATION_RECIPES_EBPF: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_bpf_64_32_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_NODYLD32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_ABS64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_BPF['R_BPF_64_ABS32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
}
|
||||
|
||||
# https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc
|
||||
_RELOCATION_RECIPES_LOONGARCH: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_NONE']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=False, calc_func=_reloc_calc_identity),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD8']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=1, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB8']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=1, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD16']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=2, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB16']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=2, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_ADD64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_value),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_SUB64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_value_minus_sym_addend),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_32_PCREL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_LOONGARCH['R_LARCH_64_PCREL']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True,
|
||||
calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
}
|
||||
|
||||
_RELOCATION_RECIPES_S390X: Mapping[int, _RELOCATION_RECIPE_TYPE] = {
|
||||
ENUM_RELOC_TYPE_S390X['R_390_32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
ENUM_RELOC_TYPE_S390X['R_390_PC32']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=4, has_addend=True, calc_func=_reloc_calc_sym_plus_addend_pcrel),
|
||||
ENUM_RELOC_TYPE_S390X['R_390_64']: _RELOCATION_RECIPE_TYPE(
|
||||
bytesize=8, has_addend=True, calc_func=_reloc_calc_sym_plus_addend),
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,628 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/sections.py
|
||||
#
|
||||
# ELF sections
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import zlib
|
||||
from functools import cached_property
|
||||
from typing import IO, TYPE_CHECKING, Any, Literal, overload
|
||||
|
||||
from ..common.exceptions import ELFCompressionError
|
||||
from ..common.utils import struct_parse, elf_assert, parse_cstring_from_stream
|
||||
from collections import defaultdict
|
||||
from .constants import SH_FLAGS
|
||||
from .notes import iter_notes
|
||||
from elftools.construct.lib.container import Container
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from .elffile import ELFFile
|
||||
from .structs import ELFStructs
|
||||
|
||||
|
||||
class Section:
|
||||
""" Base class for ELF sections. Also used for all sections types that have
|
||||
no special functionality.
|
||||
|
||||
Allows dictionary-like access to the section header. For example:
|
||||
> sec = Section(...)
|
||||
> sec['sh_type'] # section type
|
||||
"""
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
self.header = header
|
||||
self.name = name
|
||||
self.elffile = elffile
|
||||
self.stream: IO[bytes] = self.elffile.stream
|
||||
self.structs: ELFStructs = self.elffile.structs
|
||||
self._compressed: int = header['sh_flags'] & SH_FLAGS.SHF_COMPRESSED
|
||||
|
||||
if self.compressed:
|
||||
# Read the compression header now to know about the size/alignment
|
||||
# of the decompressed data.
|
||||
header = struct_parse(self.structs.Elf_Chdr,
|
||||
self.stream,
|
||||
stream_pos=self['sh_offset'])
|
||||
self._compression_type: str = header['ch_type']
|
||||
self._decompressed_size: int = header['ch_size']
|
||||
self._decompressed_align: int = header['ch_addralign']
|
||||
else:
|
||||
self._decompressed_size = header['sh_size']
|
||||
self._decompressed_align = header['sh_addralign']
|
||||
|
||||
@property
|
||||
def compressed(self) -> int:
|
||||
""" Is this section compressed?
|
||||
"""
|
||||
return self._compressed
|
||||
|
||||
@property
|
||||
def data_size(self) -> int:
|
||||
""" Return the logical size for this section's data.
|
||||
|
||||
This can be different from the .sh_size header field when the section
|
||||
is compressed.
|
||||
"""
|
||||
return self._decompressed_size
|
||||
|
||||
@property
|
||||
def data_alignment(self) -> int:
|
||||
""" Return the logical alignment for this section's data.
|
||||
|
||||
This can be different from the .sh_addralign header field when the
|
||||
section is compressed.
|
||||
"""
|
||||
return self._decompressed_align
|
||||
|
||||
def data(self) -> bytes:
|
||||
""" The section data from the file.
|
||||
|
||||
Note that data is decompressed if the stored section data is
|
||||
compressed.
|
||||
"""
|
||||
# If this section is NOBITS, there is no data. provide a dummy answer
|
||||
if self.header['sh_type'] == 'SHT_NOBITS':
|
||||
return b'\0'*self.data_size
|
||||
|
||||
# If this section is compressed, deflate it
|
||||
if self.compressed:
|
||||
c_type = self._compression_type
|
||||
if c_type == 'ELFCOMPRESS_ZLIB':
|
||||
# Read the data to decompress starting right after the
|
||||
# compression header until the end of the section.
|
||||
hdr_size: int = self.structs.Elf_Chdr.sizeof()
|
||||
self.stream.seek(self['sh_offset'] + hdr_size)
|
||||
compressed: bytes = self.stream.read(self['sh_size'] - hdr_size)
|
||||
|
||||
decomp = zlib.decompressobj()
|
||||
result = decomp.decompress(compressed, self.data_size)
|
||||
elif isinstance(c_type, int):
|
||||
raise ELFCompressionError(
|
||||
'Unknown compression type: {:#0x}'.format(c_type)
|
||||
)
|
||||
else:
|
||||
raise ELFCompressionError(
|
||||
'Unknown compression type: {!r}'.format(c_type)
|
||||
)
|
||||
|
||||
if len(result) != self._decompressed_size:
|
||||
raise ELFCompressionError(
|
||||
'Decompressed data is {} bytes long, should be {} bytes'
|
||||
' long'.format(len(result), self._decompressed_size)
|
||||
)
|
||||
else:
|
||||
self.stream.seek(self['sh_offset'])
|
||||
result = self.stream.read(self._decompressed_size)
|
||||
|
||||
return result
|
||||
|
||||
def is_null(self) -> bool:
|
||||
""" Is this a null section?
|
||||
"""
|
||||
return False
|
||||
|
||||
@overload
|
||||
def __getitem__(self, name: Literal["sh_addr", "sh_entsize", "sh_flags", "sh_offset", "sh_size"]) -> int: ...
|
||||
@overload
|
||||
def __getitem__(self, name: Literal["st_name", "sh_type"]) -> str: ...
|
||||
@overload
|
||||
def __getitem__(self, name: str) -> Any: ...
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, Section) and self.header == other.header
|
||||
|
||||
|
||||
class NullSection(Section):
|
||||
""" ELF NULL section
|
||||
"""
|
||||
def is_null(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class StringTableSection(Section):
|
||||
""" ELF string table section.
|
||||
"""
|
||||
def get_string(self, offset: int) -> str:
|
||||
""" Get the string stored at the given offset in this string table.
|
||||
"""
|
||||
table_offset = self['sh_offset']
|
||||
s = parse_cstring_from_stream(self.stream, table_offset + offset)
|
||||
return s.decode('utf-8', errors='replace') if s else ''
|
||||
|
||||
|
||||
class SymbolTableIndexSection(Section):
|
||||
""" A section containing the section header table indices corresponding
|
||||
to symbols in the linked symbol table. This section has to exist if the
|
||||
symbol table contains an entry with a section header index set to
|
||||
SHN_XINDEX (0xffff). The format of the section is described at
|
||||
https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.sheader.html
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: Container,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def get_section_index(self, n: int) -> int:
|
||||
""" Get the section header table index for the symbol with index #n.
|
||||
The section contains an array of Elf32_word values with one entry
|
||||
for every symbol in the associated symbol table.
|
||||
"""
|
||||
return struct_parse(self.elffile.structs.Elf_word(''), self.stream,
|
||||
self['sh_offset'] + n * self['sh_entsize'])
|
||||
|
||||
|
||||
class SymbolTableSection(Section):
|
||||
""" ELF symbol table section. Has an associated StringTableSection that's
|
||||
passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
stringtable: StringTableSection,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.stringtable = stringtable
|
||||
elf_assert(self['sh_entsize'] > 0,
|
||||
'Expected entry size of section %r to be > 0' % name)
|
||||
elf_assert(self['sh_size'] % self['sh_entsize'] == 0,
|
||||
'Expected section size to be a multiple of entry size in section %r' % name)
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize']
|
||||
|
||||
def get_symbol(self, n: int) -> Symbol:
|
||||
""" Get the symbol at index #n from the table (Symbol object)
|
||||
"""
|
||||
# Grab the symbol's entry from the stream
|
||||
entry_offset = self['sh_offset'] + n * self['sh_entsize']
|
||||
entry = struct_parse(
|
||||
self.structs.Elf_Sym,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
# Find the symbol name in the associated string table
|
||||
name = self.stringtable.get_string(entry['st_name'])
|
||||
return Symbol(entry, name)
|
||||
|
||||
def get_symbol_by_name(self, name: str) -> list[Symbol] | None:
|
||||
""" Get a symbol(s) by name. Return None if no symbol by the given name
|
||||
exists.
|
||||
"""
|
||||
symnums = self._symbol_name_map.get(name)
|
||||
return [self.get_symbol(i) for i in symnums] if symnums else None
|
||||
|
||||
@cached_property
|
||||
def _symbol_name_map(self) -> dict[str, list[int]]:
|
||||
smap = defaultdict(list)
|
||||
for i, sym in enumerate(self.iter_symbols()):
|
||||
smap[sym.name].append(i)
|
||||
return smap
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(self.num_symbols()):
|
||||
yield self.get_symbol(i)
|
||||
|
||||
|
||||
class Symbol:
|
||||
""" Symbol object - representing a single symbol entry from a symbol table
|
||||
section.
|
||||
|
||||
Similarly to Section objects, allows dictionary-like access to the
|
||||
symbol entry.
|
||||
"""
|
||||
def __init__(self, entry: Container, name: str) -> None:
|
||||
self.entry = entry
|
||||
self.name = name
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to entries
|
||||
"""
|
||||
return self.entry[name]
|
||||
|
||||
|
||||
class SUNWSyminfoTableSection(Section):
|
||||
""" ELF .SUNW Syminfo table section.
|
||||
Has an associated SymbolTableSection that's passed in the constructor.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
header: Container,
|
||||
name: str,
|
||||
elffile: ELFFile,
|
||||
symboltable: SymbolTableSection,
|
||||
) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
self.symboltable = symboltable
|
||||
|
||||
def num_symbols(self) -> int:
|
||||
""" Number of symbols in the table
|
||||
"""
|
||||
return self['sh_size'] // self['sh_entsize'] - 1
|
||||
|
||||
def get_symbol(self, n: int) -> Symbol:
|
||||
""" Get the symbol at index #n from the table (Symbol object).
|
||||
It begins at 1 and not 0 since the first entry is used to
|
||||
store the current version of the syminfo table.
|
||||
"""
|
||||
# Grab the symbol's entry from the stream
|
||||
entry_offset = self['sh_offset'] + n * self['sh_entsize']
|
||||
entry = struct_parse(
|
||||
self.structs.Elf_Sunw_Syminfo,
|
||||
self.stream,
|
||||
stream_pos=entry_offset)
|
||||
# Find the symbol name in the associated symbol table
|
||||
name = self.symboltable.get_symbol(n).name
|
||||
return Symbol(entry, name)
|
||||
|
||||
def iter_symbols(self) -> Iterator[Symbol]:
|
||||
""" Yield all the symbols in the table
|
||||
"""
|
||||
for i in range(1, self.num_symbols() + 1):
|
||||
yield self.get_symbol(i)
|
||||
|
||||
|
||||
class NoteSection(Section):
|
||||
""" ELF NOTE section. Knows how to parse notes.
|
||||
"""
|
||||
def iter_notes(self) -> Iterator[Container]:
|
||||
""" Yield all the notes in the section. Each result is a dictionary-
|
||||
like object with "n_name", "n_type", and "n_desc" fields, amongst
|
||||
others.
|
||||
"""
|
||||
return iter_notes(self.elffile, self['sh_offset'], self['sh_size'])
|
||||
|
||||
|
||||
class StabSection(Section):
|
||||
""" ELF stab section.
|
||||
"""
|
||||
def iter_stabs(self) -> Iterator[Container]:
|
||||
""" Yield all stab entries. Result type is ELFStructs.Elf_Stabs.
|
||||
"""
|
||||
offset: int = self['sh_offset']
|
||||
size: int = self['sh_size']
|
||||
end = offset + size
|
||||
while offset < end:
|
||||
stabs = struct_parse(
|
||||
self.structs.Elf_Stabs,
|
||||
self.stream,
|
||||
stream_pos=offset)
|
||||
stabs['n_offset'] = offset
|
||||
offset += self.structs.Elf_Stabs.sizeof()
|
||||
self.stream.seek(offset)
|
||||
yield stabs
|
||||
|
||||
class Attribute:
|
||||
""" Attribute object - representing a build attribute of ELF files.
|
||||
"""
|
||||
if TYPE_CHECKING:
|
||||
value: Any
|
||||
|
||||
def __init__(self, structs: ELFStructs, stream: IO[bytes]) -> None:
|
||||
self._tag = self._parse(structs, stream)
|
||||
self.extra: Any | None = None
|
||||
|
||||
@classmethod
|
||||
def _parse(cls, structs: ELFStructs, stream: IO[bytes]) -> Container:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def tag(self) -> str:
|
||||
return self._tag['tag']
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = '<%s (%s): %r>' % \
|
||||
(self.__class__.__name__, self.tag, self.value)
|
||||
s += ' %s' % self.extra if self.extra is not None else ''
|
||||
return s
|
||||
|
||||
|
||||
class AttributesSubsubsection(Section):
|
||||
""" Subsubsection of an ELF attribute section's subsection.
|
||||
"""
|
||||
attribute: type[Attribute]
|
||||
|
||||
def __init__(self, stream: IO[bytes], structs: ELFStructs, offset: int) -> None:
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
self.structs = structs
|
||||
|
||||
self.header: Attribute = self.attribute(self.structs, self.stream) # type: ignore[assignment]
|
||||
|
||||
self.attr_start = self.stream.tell()
|
||||
|
||||
def iter_attributes(self, tag: str | None = None) -> Iterator[Attribute]:
|
||||
""" Yield all attributes (limit to |tag| if specified).
|
||||
"""
|
||||
for attribute in self._make_attributes():
|
||||
if tag is None or attribute.tag == tag:
|
||||
yield attribute
|
||||
|
||||
@property
|
||||
def num_attributes(self) -> int:
|
||||
""" Number of attributes in the subsubsection.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_attributes()) + 1
|
||||
|
||||
@property
|
||||
def attributes(self) -> list[Attribute]:
|
||||
""" List of all attributes in the subsubsection.
|
||||
"""
|
||||
return [self.header, *(self.iter_attributes())]
|
||||
|
||||
def _make_attributes(self) -> Iterator[Attribute]:
|
||||
""" Create all attributes for this subsubsection except the first one
|
||||
which is the header.
|
||||
"""
|
||||
end = self.offset + self.header.value
|
||||
|
||||
self.stream.seek(self.attr_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
yield self.attribute(self.structs, self.stream)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = "<%s (%s): %d bytes>"
|
||||
return s % (self.__class__.__name__,
|
||||
self.header.tag[4:], self.header.value)
|
||||
|
||||
|
||||
class AttributesSubsection(Section):
|
||||
""" Subsection of an ELF attributes section.
|
||||
"""
|
||||
subsubsection = AttributesSubsubsection
|
||||
|
||||
def __init__(self, stream: IO[bytes], structs: ELFStructs, offset: int) -> None:
|
||||
self.stream = stream
|
||||
self.offset = offset
|
||||
self.structs = structs
|
||||
|
||||
self.header: Container = struct_parse(structs.Elf_Attr_Subsection_Header, self.stream, self.offset)
|
||||
|
||||
self.subsubsec_start = self.stream.tell()
|
||||
|
||||
def iter_subsubsections(self, scope: str | None = None) -> Iterator[AttributesSubsubsection]:
|
||||
""" Yield all subsubsections (limit to |scope| if specified).
|
||||
"""
|
||||
for subsubsec in self._make_subsubsections():
|
||||
if scope is None or subsubsec.header.tag == scope:
|
||||
yield subsubsec
|
||||
|
||||
@property
|
||||
def num_subsubsections(self) -> int:
|
||||
""" Number of subsubsections in the subsection.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_subsubsections())
|
||||
|
||||
@property
|
||||
def subsubsections(self) -> list[AttributesSubsubsection]:
|
||||
""" List of all subsubsections in the subsection.
|
||||
"""
|
||||
return list(self.iter_subsubsections())
|
||||
|
||||
def _make_subsubsections(self) -> Iterator[AttributesSubsubsection]:
|
||||
""" Create all subsubsections for this subsection.
|
||||
"""
|
||||
end = self.offset + self['length']
|
||||
|
||||
self.stream.seek(self.subsubsec_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
subsubsec = self.subsubsection(self.stream,
|
||||
self.structs,
|
||||
self.stream.tell())
|
||||
self.stream.seek(self.subsubsec_start + subsubsec.header.value)
|
||||
yield subsubsec
|
||||
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries.
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
s = "<%s (%s): %d bytes>"
|
||||
return s % (self.__class__.__name__,
|
||||
self.header['vendor_name'], self.header['length'])
|
||||
|
||||
|
||||
class AttributesSection(Section):
|
||||
""" ELF attributes section.
|
||||
"""
|
||||
subsection = AttributesSubsection
|
||||
|
||||
def __init__(self, header: Container, name: str, elffile: ELFFile) -> None:
|
||||
super().__init__(header, name, elffile)
|
||||
|
||||
fv: int = struct_parse(self.structs.Elf_byte('format_version'),
|
||||
self.stream,
|
||||
self['sh_offset'])
|
||||
|
||||
elf_assert(chr(fv) == 'A',
|
||||
"Unknown attributes version %s, expecting 'A'." % chr(fv))
|
||||
|
||||
self.subsec_start = self.stream.tell()
|
||||
|
||||
def iter_subsections(self, vendor_name: str | None = None) -> Iterator[AttributesSubsection]:
|
||||
""" Yield all subsections (limit to |vendor_name| if specified).
|
||||
"""
|
||||
for subsec in self._make_subsections():
|
||||
if vendor_name is None or subsec['vendor_name'] == vendor_name:
|
||||
yield subsec
|
||||
|
||||
@property
|
||||
def num_subsections(self) -> int:
|
||||
""" Number of subsections in the section.
|
||||
"""
|
||||
return sum(1 for _ in self.iter_subsections())
|
||||
|
||||
@property
|
||||
def subsections(self) -> list[AttributesSubsection]:
|
||||
""" List of all subsections in the section.
|
||||
"""
|
||||
return list(self.iter_subsections())
|
||||
|
||||
def _make_subsections(self) -> Iterator[AttributesSubsection]:
|
||||
""" Create all subsections for this section.
|
||||
"""
|
||||
end = self['sh_offset'] + self.data_size
|
||||
|
||||
self.stream.seek(self.subsec_start)
|
||||
|
||||
while self.stream.tell() != end:
|
||||
subsec = self.subsection(self.stream,
|
||||
self.structs,
|
||||
self.stream.tell())
|
||||
self.stream.seek(self.subsec_start + subsec['length'])
|
||||
yield subsec
|
||||
|
||||
|
||||
class ARMAttribute(Attribute):
|
||||
""" ARM attribute object - representing a build attribute of ARM ELF files.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _parse(cls, structs: ELFStructs, stream: IO[bytes]) -> Container:
|
||||
return struct_parse(structs.Elf_Arm_Attribute_Tag, stream)
|
||||
|
||||
def __init__(self, structs: ELFStructs, stream: IO[bytes]) -> None:
|
||||
super().__init__(structs, stream)
|
||||
|
||||
if self.tag in ('TAG_FILE', 'TAG_SECTION', 'TAG_SYMBOL'):
|
||||
self.value = struct_parse(structs.Elf_word('value'), stream)
|
||||
|
||||
if self.tag != 'TAG_FILE':
|
||||
self.extra: list[int] = []
|
||||
s_number: int = struct_parse(structs.Elf_uleb128('s_number'), stream)
|
||||
|
||||
while s_number != 0:
|
||||
self.extra.append(s_number)
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'),
|
||||
stream)
|
||||
|
||||
elif self.tag in ('TAG_CPU_RAW_NAME', 'TAG_CPU_NAME', 'TAG_CONFORMANCE'):
|
||||
self.value = struct_parse(structs.Elf_ntbs('value',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_COMPATIBILITY':
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
self.extra = struct_parse(structs.Elf_ntbs('vendor_name',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_ALSO_COMPATIBLE_WITH':
|
||||
self.value = ARMAttribute(structs, stream)
|
||||
|
||||
if type(self.value.value) is not str:
|
||||
nul: int = struct_parse(structs.Elf_byte('nul'), stream)
|
||||
elf_assert(nul == 0,
|
||||
"Invalid terminating byte %r, expecting NUL." % nul)
|
||||
|
||||
else:
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
|
||||
|
||||
class ARMAttributesSubsubsection(AttributesSubsubsection):
|
||||
""" Subsubsection of an ELF .ARM.attributes section's subsection.
|
||||
"""
|
||||
attribute = ARMAttribute
|
||||
|
||||
|
||||
class ARMAttributesSubsection(AttributesSubsection):
|
||||
""" Subsection of an ELF .ARM.attributes section.
|
||||
"""
|
||||
subsubsection = ARMAttributesSubsubsection
|
||||
|
||||
|
||||
class ARMAttributesSection(AttributesSection):
|
||||
""" ELF .ARM.attributes section.
|
||||
"""
|
||||
subsection = ARMAttributesSubsection
|
||||
|
||||
|
||||
class RISCVAttribute(Attribute):
|
||||
""" Attribute of an ELF .riscv.attributes section.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _parse(cls, structs: ELFStructs, stream: IO[bytes]) -> Container:
|
||||
return struct_parse(structs.Elf_RiscV_Attribute_Tag, stream)
|
||||
|
||||
def __init__(self, structs: ELFStructs, stream: IO[bytes]) -> None:
|
||||
super().__init__(structs, stream)
|
||||
|
||||
if self.tag in ('TAG_FILE', 'TAG_SECTION', 'TAG_SYMBOL'):
|
||||
self.value = struct_parse(structs.Elf_word('value'), stream)
|
||||
|
||||
if self.tag != 'TAG_FILE':
|
||||
self.extra: list[int] = []
|
||||
s_number: int = struct_parse(structs.Elf_uleb128('s_number'), stream)
|
||||
|
||||
while s_number != 0:
|
||||
self.extra.append(s_number)
|
||||
s_number = struct_parse(structs.Elf_uleb128('s_number'),
|
||||
stream)
|
||||
|
||||
elif self.tag == 'TAG_ARCH':
|
||||
self.value = struct_parse(structs.Elf_ntbs('value',
|
||||
encoding='utf-8'),
|
||||
stream)
|
||||
|
||||
else:
|
||||
self.value = struct_parse(structs.Elf_uleb128('value'), stream)
|
||||
|
||||
|
||||
class RISCVAttributesSubsubsection(AttributesSubsubsection):
|
||||
""" Subsubsection of an ELF .riscv.attributes subsection.
|
||||
"""
|
||||
attribute = RISCVAttribute
|
||||
|
||||
|
||||
class RISCVAttributesSubsection(AttributesSubsection):
|
||||
""" Subsection of an ELF .riscv.attributes section.
|
||||
"""
|
||||
subsubsection = RISCVAttributesSubsubsection
|
||||
|
||||
|
||||
class RISCVAttributesSection(AttributesSection):
|
||||
""" ELF .riscv.attributes section.
|
||||
"""
|
||||
subsection = RISCVAttributesSubsection
|
||||
@@ -0,0 +1,143 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/segments.py
|
||||
#
|
||||
# ELF segments
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO, TYPE_CHECKING, Any, Literal, overload
|
||||
|
||||
from ..construct import CString
|
||||
from ..common.utils import struct_parse
|
||||
from .constants import SH_FLAGS
|
||||
from .notes import iter_notes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
from ..construct import Container
|
||||
from .elffile import ELFFile
|
||||
from .sections import Section
|
||||
|
||||
|
||||
class Segment:
|
||||
def __init__(self, header: Container, stream: IO[bytes]) -> None:
|
||||
self.header = header
|
||||
self.stream = stream
|
||||
|
||||
def data(self) -> bytes:
|
||||
""" The segment data from the file.
|
||||
"""
|
||||
self.stream.seek(self['p_offset'])
|
||||
return self.stream.read(self['p_filesz'])
|
||||
|
||||
@overload
|
||||
def __getitem__(self, name: Literal["p_filesz", "p_memsz", "p_offset", "p_vaddr"]) -> int: ...
|
||||
@overload
|
||||
def __getitem__(self, name: Literal["p_type"]) -> str: ...
|
||||
@overload
|
||||
def __getitem__(self, name: str) -> Any: ...
|
||||
def __getitem__(self, name: str) -> Any:
|
||||
""" Implement dict-like access to header entries
|
||||
"""
|
||||
return self.header[name]
|
||||
|
||||
def section_in_segment(self, section: Section) -> bool:
|
||||
""" Is the given section contained in this segment?
|
||||
|
||||
Note: this tries to reproduce the intricate rules of the
|
||||
ELF_SECTION_IN_SEGMENT_STRICT macro of the header
|
||||
elf/include/internal.h in the source of binutils.
|
||||
"""
|
||||
# Only the 'strict' checks from ELF_SECTION_IN_SEGMENT_1 are included
|
||||
segtype: str = self['p_type']
|
||||
sectype: str = section['sh_type']
|
||||
secflags: int = section['sh_flags']
|
||||
|
||||
# Only PT_LOAD, PT_GNU_RELRO and PT_TLS segments can contain SHF_TLS
|
||||
# sections
|
||||
if ( secflags & SH_FLAGS.SHF_TLS and
|
||||
segtype in ('PT_TLS', 'PT_GNU_RELRO', 'PT_LOAD')):
|
||||
pass
|
||||
# PT_TLS segment contains only SHF_TLS sections, PT_PHDR no sections
|
||||
# at all
|
||||
elif ( (secflags & SH_FLAGS.SHF_TLS) == 0 and
|
||||
segtype not in ('PT_TLS', 'PT_PHDR')):
|
||||
pass
|
||||
else:
|
||||
return False
|
||||
|
||||
# PT_LOAD and similar segments only have SHF_ALLOC sections.
|
||||
if ( (secflags & SH_FLAGS.SHF_ALLOC) == 0 and
|
||||
segtype in ('PT_LOAD', 'PT_DYNAMIC', 'PT_GNU_EH_FRAME',
|
||||
'PT_GNU_RELRO', 'PT_GNU_STACK')):
|
||||
return False
|
||||
|
||||
# In ELF_SECTION_IN_SEGMENT_STRICT the flag check_vma is on, so if
|
||||
# this is an alloc section, check whether its VMA is in bounds.
|
||||
if secflags & SH_FLAGS.SHF_ALLOC:
|
||||
secaddr: int = section['sh_addr']
|
||||
vaddr: int = self['p_vaddr']
|
||||
|
||||
# This checks that the section is wholly contained in the segment.
|
||||
# The third condition is the 'strict' one - an empty section will
|
||||
# not match at the very end of the segment (unless the segment is
|
||||
# also zero size, which is handled by the second condition).
|
||||
|
||||
# Seva 2024-07-12: a zero length section at a zero offset
|
||||
# in a zero length segment should match - in GNU readelf, p_memsz
|
||||
# is unsigned, on a zero length segment p_memsz-1 wraps around
|
||||
# and the third condition matches.
|
||||
if not (secaddr >= vaddr and
|
||||
secaddr - vaddr + section['sh_size'] <= self['p_memsz'] and
|
||||
(self['p_memsz'] == 0 or secaddr - vaddr <= self['p_memsz'] - 1)):
|
||||
return False
|
||||
|
||||
# If we've come this far and it's a NOBITS section, it's in the segment
|
||||
if sectype == 'SHT_NOBITS':
|
||||
return True
|
||||
|
||||
secoffset: int = section['sh_offset']
|
||||
poffset: int = self['p_offset']
|
||||
|
||||
# Same logic as with secaddr vs. vaddr checks above, just on offsets in
|
||||
# the file
|
||||
# Seva 2024-07-12: similar discrepancy with readelf from unsignedness of p_filesz
|
||||
return (secoffset >= poffset and
|
||||
secoffset - poffset + section['sh_size'] <= self['p_filesz'] and
|
||||
(self['p_filesz'] == 0 or secoffset - poffset <= self['p_filesz'] - 1))
|
||||
|
||||
|
||||
class InterpSegment(Segment):
|
||||
""" INTERP segment. Knows how to obtain the path to the interpreter used
|
||||
for this ELF file.
|
||||
"""
|
||||
def __init__(self, header: Container, stream: IO[bytes]) -> None:
|
||||
super().__init__(header, stream)
|
||||
|
||||
def get_interp_name(self) -> str:
|
||||
""" Obtain the interpreter path used for this ELF file.
|
||||
"""
|
||||
path_offset: int = self['p_offset']
|
||||
return struct_parse(
|
||||
CString('', encoding='utf-8'),
|
||||
self.stream,
|
||||
stream_pos=path_offset)
|
||||
|
||||
|
||||
class NoteSegment(Segment):
|
||||
""" NOTE segment. Knows how to parse notes.
|
||||
"""
|
||||
def __init__(self, header: Container, stream: IO[bytes], elffile: ELFFile) -> None:
|
||||
super().__init__(header, stream)
|
||||
self.elffile = elffile
|
||||
|
||||
def iter_notes(self) -> Iterator[Container]:
|
||||
""" Yield all the notes in the segment. Each result is a dictionary-
|
||||
like object with "n_name", "n_type", and "n_desc" fields, amongst
|
||||
others.
|
||||
"""
|
||||
return iter_notes(self.elffile, self['p_offset'], self['p_filesz'])
|
||||
@@ -0,0 +1,600 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# elftools: elf/structs.py
|
||||
#
|
||||
# Encapsulation of Construct structs for parsing an ELF file, adjusted for
|
||||
# correct endianness and word-size.
|
||||
#
|
||||
# Eli Bendersky (eliben@gmail.com)
|
||||
# This code is in the public domain
|
||||
#-------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import elftools.elf .enums as e
|
||||
from ..construct import (
|
||||
UBInt8, UBInt16, UBInt32, UBInt64,
|
||||
ULInt8, ULInt16, ULInt32, ULInt64,
|
||||
SBInt32, SLInt32, SBInt64, SLInt64,
|
||||
Struct, Array, Enum, Padding, BitStruct, BitField, Value, String, CString,
|
||||
Switch, Field
|
||||
)
|
||||
from ..common.construct_utils import ULEB128
|
||||
from ..common.utils import roundup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from ..construct.core import FormatField
|
||||
from ..construct.lib.container import Container
|
||||
|
||||
|
||||
class ELFStructs:
|
||||
""" Accessible attributes:
|
||||
|
||||
Elf_{byte|half|word|word64|addr|offset|sword|xword|xsword}:
|
||||
Data chunks, as specified by the ELF standard, adjusted for
|
||||
correct endianness and word-size.
|
||||
|
||||
Elf_Ehdr:
|
||||
ELF file header
|
||||
|
||||
Elf_Phdr:
|
||||
Program header
|
||||
|
||||
Elf_Shdr:
|
||||
Section header
|
||||
|
||||
Elf_Sym:
|
||||
Symbol table entry
|
||||
|
||||
Elf_Rel, Elf_Rela:
|
||||
Entries in relocation sections
|
||||
"""
|
||||
if TYPE_CHECKING:
|
||||
# type hints for dynamically defined instance variables
|
||||
Elf_byte: Callable[[str], FormatField[int]]
|
||||
Elf_half: Callable[[str], FormatField[int]]
|
||||
Elf_word: Callable[[str], FormatField[int]]
|
||||
Elf_word64: Callable[[str], FormatField[int]]
|
||||
Elf_addr: Callable[[str], FormatField[int]]
|
||||
Elf_offset: Callable[[str], FormatField[int]]
|
||||
Elf_sword: Callable[[str], FormatField[int]]
|
||||
Elf_sxword: Callable[[str], FormatField[int]]
|
||||
Elf_xsword: Callable[[str], FormatField[int]]
|
||||
Elf_Ehdr: Struct
|
||||
Elf_Phdr: Struct
|
||||
Elf_Shdr: Struct
|
||||
Elf_Sym: Struct
|
||||
Elf_Rel: Struct
|
||||
Elf_Rela: Struct
|
||||
|
||||
def __init__(self, little_endian: bool = True, elfclass: int = 32) -> None:
|
||||
assert elfclass == 32 or elfclass == 64
|
||||
self.little_endian = little_endian
|
||||
self.elfclass = elfclass
|
||||
self.e_type: str | None = None # e.ENUM_E_TYPE
|
||||
self.e_machine: str | None = None # e.ENUM_E_MACHINE
|
||||
self.e_ident_osabi: str | None = None # e.ENUM_E_VERSION
|
||||
|
||||
def __getstate__(self) -> tuple[bool, int, str | None, str | None, str | None]:
|
||||
return self.little_endian, self.elfclass, self.e_type, self.e_machine, self.e_ident_osabi
|
||||
|
||||
def __setstate__(self, state: tuple[bool, int, str | None, str | None, str | None]) -> None:
|
||||
self.little_endian, self.elfclass, e_type, e_machine, e_osabi = state
|
||||
self.create_basic_structs()
|
||||
self.create_advanced_structs(e_type, e_machine, e_osabi)
|
||||
|
||||
def create_basic_structs(self) -> None:
|
||||
""" Create word-size related structs and ehdr struct needed for
|
||||
initial determining of ELF type.
|
||||
"""
|
||||
if self.little_endian:
|
||||
self.Elf_byte = ULInt8
|
||||
self.Elf_half = ULInt16
|
||||
self.Elf_word = ULInt32
|
||||
self.Elf_word64 = ULInt64
|
||||
self.Elf_addr = ULInt32 if self.elfclass == 32 else ULInt64
|
||||
self.Elf_offset = self.Elf_addr
|
||||
self.Elf_sword = SLInt32
|
||||
self.Elf_xword = ULInt32 if self.elfclass == 32 else ULInt64
|
||||
self.Elf_sxword = SLInt32 if self.elfclass == 32 else SLInt64
|
||||
else:
|
||||
self.Elf_byte = UBInt8
|
||||
self.Elf_half = UBInt16
|
||||
self.Elf_word = UBInt32
|
||||
self.Elf_word64 = UBInt64
|
||||
self.Elf_addr = UBInt32 if self.elfclass == 32 else UBInt64
|
||||
self.Elf_offset = self.Elf_addr
|
||||
self.Elf_sword = SBInt32
|
||||
self.Elf_xword = UBInt32 if self.elfclass == 32 else UBInt64
|
||||
self.Elf_sxword = SBInt32 if self.elfclass == 32 else SBInt64
|
||||
self._create_ehdr()
|
||||
self._create_leb128()
|
||||
self._create_ntbs()
|
||||
|
||||
def create_advanced_structs(self, e_type: str | None = None, e_machine: str | None = None, e_ident_osabi: str | None = None) -> None:
|
||||
""" Create all ELF structs except the ehdr. They may possibly depend
|
||||
on provided e_type and/or e_machine parsed from ehdr.
|
||||
"""
|
||||
self.e_type = e_type
|
||||
self.e_machine = e_machine
|
||||
self.e_ident_osabi = e_ident_osabi
|
||||
|
||||
self._create_phdr()
|
||||
self._create_shdr()
|
||||
self._create_chdr()
|
||||
self._create_sym()
|
||||
self._create_rel()
|
||||
self._create_dyn()
|
||||
self._create_sunw_syminfo()
|
||||
self._create_gnu_verneed()
|
||||
self._create_gnu_verdef()
|
||||
self._create_gnu_versym()
|
||||
self._create_gnu_abi()
|
||||
self._create_gnu_property()
|
||||
self._create_note(e_type)
|
||||
self._create_stabs()
|
||||
self._create_attributes_subsection()
|
||||
self._create_arm_attributes()
|
||||
self._create_riscv_attributes()
|
||||
self._create_elf_hash()
|
||||
self._create_gnu_hash()
|
||||
self._create_gnu_debuglink()
|
||||
|
||||
#-------------------------------- PRIVATE --------------------------------#
|
||||
|
||||
def _create_ehdr(self) -> None:
|
||||
self.Elf_Ehdr = Struct('Elf_Ehdr',
|
||||
Struct('e_ident',
|
||||
Array(4, self.Elf_byte('EI_MAG')),
|
||||
Enum(self.Elf_byte('EI_CLASS'), **e.ENUM_EI_CLASS),
|
||||
Enum(self.Elf_byte('EI_DATA'), **e.ENUM_EI_DATA),
|
||||
Enum(self.Elf_byte('EI_VERSION'), **e.ENUM_E_VERSION),
|
||||
Enum(self.Elf_byte('EI_OSABI'), **e.ENUM_EI_OSABI),
|
||||
self.Elf_byte('EI_ABIVERSION'),
|
||||
Padding(7)
|
||||
),
|
||||
Enum(self.Elf_half('e_type'), **e.ENUM_E_TYPE),
|
||||
Enum(self.Elf_half('e_machine'), **e.ENUM_E_MACHINE),
|
||||
Enum(self.Elf_word('e_version'), **e.ENUM_E_VERSION),
|
||||
self.Elf_addr('e_entry'),
|
||||
self.Elf_offset('e_phoff'),
|
||||
self.Elf_offset('e_shoff'),
|
||||
self.Elf_word('e_flags'),
|
||||
self.Elf_half('e_ehsize'),
|
||||
self.Elf_half('e_phentsize'),
|
||||
self.Elf_half('e_phnum'),
|
||||
self.Elf_half('e_shentsize'),
|
||||
self.Elf_half('e_shnum'),
|
||||
self.Elf_half('e_shstrndx'),
|
||||
)
|
||||
|
||||
def _create_leb128(self) -> None:
|
||||
self.Elf_uleb128 = ULEB128
|
||||
|
||||
def _create_ntbs(self) -> None:
|
||||
self.Elf_ntbs = CString
|
||||
|
||||
def _create_phdr(self) -> None:
|
||||
p_type_dict = e.ENUM_P_TYPE_BASE
|
||||
if self.e_machine == 'EM_ARM':
|
||||
p_type_dict = e.ENUM_P_TYPE_ARM
|
||||
elif self.e_machine == 'EM_AARCH64':
|
||||
p_type_dict = e.ENUM_P_TYPE_AARCH64
|
||||
elif self.e_machine == 'EM_MIPS':
|
||||
p_type_dict = e.ENUM_P_TYPE_MIPS
|
||||
elif self.e_machine == 'EM_RISCV':
|
||||
p_type_dict = e.ENUM_P_TYPE_RISCV
|
||||
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Phdr = Struct('Elf_Phdr',
|
||||
Enum(self.Elf_word('p_type'), **p_type_dict),
|
||||
self.Elf_offset('p_offset'),
|
||||
self.Elf_addr('p_vaddr'),
|
||||
self.Elf_addr('p_paddr'),
|
||||
self.Elf_word('p_filesz'),
|
||||
self.Elf_word('p_memsz'),
|
||||
self.Elf_word('p_flags'),
|
||||
self.Elf_word('p_align'),
|
||||
)
|
||||
else: # 64
|
||||
self.Elf_Phdr = Struct('Elf_Phdr',
|
||||
Enum(self.Elf_word('p_type'), **p_type_dict),
|
||||
self.Elf_word('p_flags'),
|
||||
self.Elf_offset('p_offset'),
|
||||
self.Elf_addr('p_vaddr'),
|
||||
self.Elf_addr('p_paddr'),
|
||||
self.Elf_xword('p_filesz'),
|
||||
self.Elf_xword('p_memsz'),
|
||||
self.Elf_xword('p_align'),
|
||||
)
|
||||
|
||||
def _create_shdr(self) -> None:
|
||||
"""Section header parsing.
|
||||
|
||||
Depends on e_machine because of machine-specific values in sh_type.
|
||||
"""
|
||||
sh_type_dict = e.ENUM_SH_TYPE_BASE
|
||||
if self.e_machine == 'EM_ARM':
|
||||
sh_type_dict = e.ENUM_SH_TYPE_ARM
|
||||
elif self.e_machine == 'EM_AARCH64':
|
||||
sh_type_dict = e.ENUM_SH_TYPE_AARCH64
|
||||
elif self.e_machine == 'EM_X86_64':
|
||||
sh_type_dict = e.ENUM_SH_TYPE_AMD64
|
||||
elif self.e_machine == 'EM_MIPS':
|
||||
sh_type_dict = e.ENUM_SH_TYPE_MIPS
|
||||
if self.e_machine == 'EM_RISCV':
|
||||
sh_type_dict = e.ENUM_SH_TYPE_RISCV
|
||||
|
||||
self.Elf_Shdr = Struct('Elf_Shdr',
|
||||
self.Elf_word('sh_name'),
|
||||
Enum(self.Elf_word('sh_type'), **sh_type_dict),
|
||||
self.Elf_xword('sh_flags'),
|
||||
self.Elf_addr('sh_addr'),
|
||||
self.Elf_offset('sh_offset'),
|
||||
self.Elf_xword('sh_size'),
|
||||
self.Elf_word('sh_link'),
|
||||
self.Elf_word('sh_info'),
|
||||
self.Elf_xword('sh_addralign'),
|
||||
self.Elf_xword('sh_entsize'),
|
||||
)
|
||||
|
||||
def _create_chdr(self) -> None:
|
||||
# Structure of compressed sections header. It is documented in Oracle
|
||||
# "Linker and Libraries Guide", Part IV ELF Application Binary
|
||||
# Interface, Chapter 13 Object File Format, Section Compression:
|
||||
# https://docs.oracle.com/cd/E53394_01/html/E54813/section_compression.html
|
||||
fields = [
|
||||
Enum(self.Elf_word('ch_type'), **e.ENUM_ELFCOMPRESS_TYPE),
|
||||
self.Elf_xword('ch_size'),
|
||||
self.Elf_xword('ch_addralign'),
|
||||
]
|
||||
if self.elfclass == 64:
|
||||
fields.insert(1, self.Elf_word('ch_reserved'))
|
||||
self.Elf_Chdr = Struct('Elf_Chdr', *fields)
|
||||
|
||||
def _create_rel(self) -> None:
|
||||
# r_info is also taken apart into r_info_sym and r_info_type. This is
|
||||
# done in Value to avoid endianity issues while parsing.
|
||||
if self.elfclass == 32:
|
||||
fields = [self.Elf_xword('r_info'),
|
||||
Value('r_info_sym',
|
||||
lambda ctx: (ctx['r_info'] >> 8) & 0xFFFFFF),
|
||||
Value('r_info_type',
|
||||
lambda ctx: ctx['r_info'] & 0xFF)]
|
||||
elif self.e_machine == 'EM_MIPS': # ELF64 MIPS
|
||||
fields = [
|
||||
# The MIPS ELF64 specification
|
||||
# (https://www.linux-mips.org/pub/linux/mips/doc/ABI/elf64-2.4.pdf)
|
||||
# provides a non-standard relocation structure definition.
|
||||
self.Elf_word('r_sym'),
|
||||
self.Elf_byte('r_ssym'),
|
||||
self.Elf_byte('r_type3'),
|
||||
self.Elf_byte('r_type2'),
|
||||
self.Elf_byte('r_type'),
|
||||
|
||||
# Synthetize usual fields for compatibility with other
|
||||
# architectures. This allows relocation consumers (including
|
||||
# our readelf tests) to work without worrying about MIPS64
|
||||
# oddities.
|
||||
Value('r_info_sym', lambda ctx: ctx['r_sym']),
|
||||
Value('r_info_ssym', lambda ctx: ctx['r_ssym']),
|
||||
Value('r_info_type', lambda ctx: ctx['r_type']),
|
||||
Value('r_info_type2', lambda ctx: ctx['r_type2']),
|
||||
Value('r_info_type3', lambda ctx: ctx['r_type3']),
|
||||
Value('r_info',
|
||||
lambda ctx: (ctx['r_sym'] << 32)
|
||||
| (ctx['r_ssym'] << 24)
|
||||
| (ctx['r_type3'] << 16)
|
||||
| (ctx['r_type2'] << 8)
|
||||
| ctx['r_type']),
|
||||
]
|
||||
else: # Other 64 ELFs
|
||||
fields = [self.Elf_xword('r_info'),
|
||||
Value('r_info_sym',
|
||||
lambda ctx: (ctx['r_info'] >> 32) & 0xFFFFFFFF),
|
||||
Value('r_info_type',
|
||||
lambda ctx: ctx['r_info'] & 0xFFFFFFFF)]
|
||||
|
||||
self.Elf_Rel = Struct('Elf_Rel',
|
||||
self.Elf_addr('r_offset'),
|
||||
*fields)
|
||||
|
||||
fields_and_addend = [*fields, self.Elf_sxword('r_addend')]
|
||||
self.Elf_Rela = Struct('Elf_Rela',
|
||||
self.Elf_addr('r_offset'),
|
||||
*fields_and_addend
|
||||
)
|
||||
|
||||
# Elf32_Relr is typedef'd as Elf32_Word, Elf64_Relr as Elf64_Xword
|
||||
# (see the glibc patch, for example:
|
||||
# https://sourceware.org/pipermail/libc-alpha/2021-October/132029.html)
|
||||
# For us, this is the same as self.Elf_addr (or self.Elf_xword).
|
||||
self.Elf_Relr = Struct('Elf_Relr', self.Elf_addr('r_offset'))
|
||||
|
||||
def _create_dyn(self) -> None:
|
||||
d_tag_dict = dict(e.ENUM_D_TAG_COMMON)
|
||||
if self.e_machine in e.ENUMMAP_EXTRA_D_TAG_MACHINE:
|
||||
assert self.e_machine is not None
|
||||
d_tag_dict.update(e.ENUMMAP_EXTRA_D_TAG_MACHINE[self.e_machine])
|
||||
elif self.e_ident_osabi == 'ELFOSABI_SOLARIS':
|
||||
d_tag_dict.update(e.ENUM_D_TAG_SOLARIS)
|
||||
|
||||
self.Elf_Dyn = Struct('Elf_Dyn',
|
||||
Enum(self.Elf_sxword('d_tag'), **d_tag_dict),
|
||||
self.Elf_xword('d_val'),
|
||||
Value('d_ptr', lambda ctx: ctx['d_val']),
|
||||
)
|
||||
|
||||
def _create_sym(self) -> None:
|
||||
# st_info is hierarchical. To access the type, use
|
||||
# container['st_info']['type']
|
||||
st_info_struct = BitStruct('st_info',
|
||||
Enum(BitField('bind', 4), **e.ENUM_ST_INFO_BIND),
|
||||
Enum(BitField('type', 4), **e.ENUM_ST_INFO_TYPE))
|
||||
# st_other is hierarchical. To access the visibility,
|
||||
# use container['st_other']['visibility']
|
||||
st_other_struct = BitStruct('st_other',
|
||||
# https://openpowerfoundation.org/wp-content/uploads/2016/03/ABI64BitOpenPOWERv1.1_16July2015_pub4.pdf
|
||||
# See 3.4.1 Symbol Values.
|
||||
Enum(BitField('local', 3), **e.ENUM_ST_LOCAL),
|
||||
Padding(2),
|
||||
Enum(BitField('visibility', 3), **e.ENUM_ST_VISIBILITY))
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Sym = Struct('Elf_Sym',
|
||||
self.Elf_word('st_name'),
|
||||
self.Elf_addr('st_value'),
|
||||
self.Elf_word('st_size'),
|
||||
st_info_struct,
|
||||
st_other_struct,
|
||||
Enum(self.Elf_half('st_shndx'), **e.ENUM_ST_SHNDX),
|
||||
)
|
||||
else:
|
||||
self.Elf_Sym = Struct('Elf_Sym',
|
||||
self.Elf_word('st_name'),
|
||||
st_info_struct,
|
||||
st_other_struct,
|
||||
Enum(self.Elf_half('st_shndx'), **e.ENUM_ST_SHNDX),
|
||||
self.Elf_addr('st_value'),
|
||||
self.Elf_xword('st_size'),
|
||||
)
|
||||
|
||||
def _create_sunw_syminfo(self) -> None:
|
||||
self.Elf_Sunw_Syminfo = Struct('Elf_Sunw_Syminfo',
|
||||
Enum(self.Elf_half('si_boundto'), **e.ENUM_SUNW_SYMINFO_BOUNDTO),
|
||||
self.Elf_half('si_flags'),
|
||||
)
|
||||
|
||||
def _create_gnu_verneed(self) -> None:
|
||||
# Structure of "version needed" entries is documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Verneed = Struct('Elf_Verneed',
|
||||
self.Elf_half('vn_version'),
|
||||
self.Elf_half('vn_cnt'),
|
||||
self.Elf_word('vn_file'),
|
||||
self.Elf_word('vn_aux'),
|
||||
self.Elf_word('vn_next'),
|
||||
)
|
||||
self.Elf_Vernaux = Struct('Elf_Vernaux',
|
||||
self.Elf_word('vna_hash'),
|
||||
self.Elf_half('vna_flags'),
|
||||
self.Elf_half('vna_other'),
|
||||
self.Elf_word('vna_name'),
|
||||
self.Elf_word('vna_next'),
|
||||
)
|
||||
|
||||
def _create_gnu_verdef(self) -> None:
|
||||
# Structure of "version definition" entries are documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Verdef = Struct('Elf_Verdef',
|
||||
self.Elf_half('vd_version'),
|
||||
self.Elf_half('vd_flags'),
|
||||
self.Elf_half('vd_ndx'),
|
||||
self.Elf_half('vd_cnt'),
|
||||
self.Elf_word('vd_hash'),
|
||||
self.Elf_word('vd_aux'),
|
||||
self.Elf_word('vd_next'),
|
||||
)
|
||||
self.Elf_Verdaux = Struct('Elf_Verdaux',
|
||||
self.Elf_word('vda_name'),
|
||||
self.Elf_word('vda_next'),
|
||||
)
|
||||
|
||||
def _create_gnu_versym(self) -> None:
|
||||
# Structure of "version symbol" entries are documented in
|
||||
# Oracle "Linker and Libraries Guide", Chapter 13 Object File Format
|
||||
self.Elf_Versym = Struct('Elf_Versym',
|
||||
Enum(self.Elf_half('ndx'), **e.ENUM_VERSYM),
|
||||
)
|
||||
|
||||
def _create_gnu_abi(self) -> None:
|
||||
# Structure of GNU ABI notes is documented in
|
||||
# https://code.woboq.org/userspace/glibc/csu/abi-note.S.html
|
||||
self.Elf_abi = Struct('Elf_abi',
|
||||
Enum(self.Elf_word('abi_os'), **e.ENUM_NOTE_ABI_TAG_OS),
|
||||
self.Elf_word('abi_major'),
|
||||
self.Elf_word('abi_minor'),
|
||||
self.Elf_word('abi_tiny'),
|
||||
)
|
||||
|
||||
def _create_gnu_debugaltlink(self) -> None:
|
||||
self.Elf_debugaltlink = Struct('Elf_debugaltlink',
|
||||
CString("sup_filename"),
|
||||
String("sup_checksum", length=20))
|
||||
|
||||
def _create_gnu_property(self) -> None:
|
||||
# Structure of GNU property notes is documented in
|
||||
# https://github.com/hjl-tools/linux-abi/wiki/linux-abi-draft.pdf
|
||||
def roundup_padding(ctx: Container) -> int:
|
||||
if self.elfclass == 32:
|
||||
return roundup(ctx.pr_datasz, 2) - ctx.pr_datasz
|
||||
return roundup(ctx.pr_datasz, 3) - ctx.pr_datasz
|
||||
|
||||
def classify_pr_data(ctx: Container) -> tuple[str, int, int] | None:
|
||||
if type(ctx.pr_type) is not str:
|
||||
return None
|
||||
if ctx.pr_type.startswith('GNU_PROPERTY_X86_'):
|
||||
return ('GNU_PROPERTY_X86_*', 4, 0)
|
||||
elif ctx.pr_type.startswith('GNU_PROPERTY_AARCH64_'):
|
||||
return ('GNU_PROPERTY_AARCH64_*', 4, 0)
|
||||
elif ctx.pr_type.startswith('GNU_PROPERTY_RISCV_'):
|
||||
return ('GNU_PROPERTY_RISCV_*', 4, 0)
|
||||
return (ctx.pr_type, ctx.pr_datasz, self.elfclass)
|
||||
|
||||
self.Elf_Prop = Struct('Elf_Prop',
|
||||
Enum(self.Elf_word('pr_type'), **e.ENUM_NOTE_GNU_PROPERTY_TYPE),
|
||||
self.Elf_word('pr_datasz'),
|
||||
Switch[tuple[str, int, int] | None]('pr_data', classify_pr_data, {
|
||||
('GNU_PROPERTY_STACK_SIZE', 4, 32): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_STACK_SIZE', 8, 64): self.Elf_word64('pr_data'),
|
||||
('GNU_PROPERTY_X86_*', 4, 0): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_AARCH64_*', 4, 0): self.Elf_word('pr_data'),
|
||||
('GNU_PROPERTY_RISCV_*', 4, 0): self.Elf_word('pr_data'),
|
||||
},
|
||||
default=Field('pr_data', lambda ctx: ctx.pr_datasz)
|
||||
),
|
||||
Padding(roundup_padding)
|
||||
)
|
||||
|
||||
def _create_note(self, e_type: str | None = None) -> None:
|
||||
# Structure of "PT_NOTE" section
|
||||
|
||||
self.Elf_ugid = self.Elf_half if self.elfclass == 32 and self.e_machine in {
|
||||
'EM_MN10300',
|
||||
'EM_ARM',
|
||||
'EM_CRIS',
|
||||
'EM_CYGNUS_FRV',
|
||||
'EM_386',
|
||||
'EM_M32R',
|
||||
'EM_68K',
|
||||
'EM_S390',
|
||||
'EM_SH',
|
||||
'EM_SPARC',
|
||||
} else self.Elf_word
|
||||
|
||||
self.Elf_Nhdr = Struct('Elf_Nhdr',
|
||||
self.Elf_word('n_namesz'),
|
||||
self.Elf_word('n_descsz'),
|
||||
Enum(self.Elf_word('n_type'),
|
||||
**(e.ENUM_NOTE_N_TYPE if e_type != "ET_CORE"
|
||||
else e.ENUM_CORE_NOTE_N_TYPE)),
|
||||
)
|
||||
|
||||
# A process psinfo structure according to
|
||||
# http://elixir.free-electrons.com/linux/v2.6.35/source/include/linux/elfcore.h#L84
|
||||
if self.elfclass == 32:
|
||||
self.Elf_Prpsinfo = Struct('Elf_Prpsinfo',
|
||||
self.Elf_byte('pr_state'),
|
||||
String('pr_sname', 1),
|
||||
self.Elf_byte('pr_zomb'),
|
||||
self.Elf_byte('pr_nice'),
|
||||
self.Elf_xword('pr_flag'),
|
||||
self.Elf_ugid('pr_uid'),
|
||||
self.Elf_ugid('pr_gid'),
|
||||
self.Elf_word('pr_pid'),
|
||||
self.Elf_word('pr_ppid'),
|
||||
self.Elf_word('pr_pgrp'),
|
||||
self.Elf_word('pr_sid'),
|
||||
String('pr_fname', 16),
|
||||
String('pr_psargs', 80),
|
||||
)
|
||||
else: # 64
|
||||
self.Elf_Prpsinfo = Struct('Elf_Prpsinfo',
|
||||
self.Elf_byte('pr_state'),
|
||||
String('pr_sname', 1),
|
||||
self.Elf_byte('pr_zomb'),
|
||||
self.Elf_byte('pr_nice'),
|
||||
Padding(4),
|
||||
self.Elf_xword('pr_flag'),
|
||||
self.Elf_ugid('pr_uid'),
|
||||
self.Elf_ugid('pr_gid'),
|
||||
self.Elf_word('pr_pid'),
|
||||
self.Elf_word('pr_ppid'),
|
||||
self.Elf_word('pr_pgrp'),
|
||||
self.Elf_word('pr_sid'),
|
||||
String('pr_fname', 16),
|
||||
String('pr_psargs', 80),
|
||||
)
|
||||
|
||||
# A PT_NOTE of type NT_FILE matching the definition in
|
||||
# https://chromium.googlesource.com/
|
||||
# native_client/nacl-binutils/+/upstream/master/binutils/readelf.c
|
||||
# Line 15121
|
||||
self.Elf_Nt_File = Struct('Elf_Nt_File',
|
||||
self.Elf_xword("num_map_entries"),
|
||||
self.Elf_xword("page_size"),
|
||||
Array(lambda ctx: ctx.num_map_entries,
|
||||
Struct('Elf_Nt_File_Entry',
|
||||
self.Elf_addr('vm_start'),
|
||||
self.Elf_addr('vm_end'),
|
||||
self.Elf_offset('page_offset'))),
|
||||
Array(lambda ctx: ctx.num_map_entries,
|
||||
CString('filename')))
|
||||
|
||||
def _create_stabs(self) -> None:
|
||||
# Structure of one stabs entry, see binutils/bfd/stabs.c
|
||||
# Names taken from https://sourceware.org/gdb/current/onlinedocs/stabs.html#Overview
|
||||
self.Elf_Stabs = Struct('Elf_Stabs',
|
||||
self.Elf_word('n_strx'),
|
||||
self.Elf_byte('n_type'),
|
||||
self.Elf_byte('n_other'),
|
||||
self.Elf_half('n_desc'),
|
||||
self.Elf_word('n_value'),
|
||||
)
|
||||
|
||||
def _create_attributes_subsection(self) -> None:
|
||||
# Structure of a build attributes subsection header. A subsection is
|
||||
# either public to all tools that process the ELF file or private to
|
||||
# the vendor's tools.
|
||||
self.Elf_Attr_Subsection_Header = Struct('Elf_Attr_Subsection',
|
||||
self.Elf_word('length'),
|
||||
self.Elf_ntbs('vendor_name',
|
||||
encoding='utf-8')
|
||||
)
|
||||
|
||||
def _create_arm_attributes(self) -> None:
|
||||
# Structure of an ARM build attribute tag.
|
||||
self.Elf_Arm_Attribute_Tag = Struct('Elf_Arm_Attribute_Tag',
|
||||
Enum(self.Elf_uleb128('tag'),
|
||||
**e.ENUM_ATTR_TAG_ARM)
|
||||
)
|
||||
|
||||
def _create_riscv_attributes(self) -> None:
|
||||
# Structure of a RISC-V build attribute tag.
|
||||
self.Elf_RiscV_Attribute_Tag = Struct('Elf_RiscV_Attribute_Tag',
|
||||
Enum(self.Elf_uleb128('tag'),
|
||||
**e.ENUM_ATTR_TAG_RISCV)
|
||||
)
|
||||
|
||||
def _create_elf_hash(self) -> None:
|
||||
# Structure of the old SYSV-style hash table header. It is documented
|
||||
# in the Oracle "Linker and Libraries Guide", Part IV ELF Application
|
||||
# Binary Interface, Chapter 14 Object File Format, Section Hash Table
|
||||
# Section:
|
||||
# https://docs.oracle.com/cd/E53394_01/html/E54813/chapter6-48031.html
|
||||
|
||||
self.Elf_Hash = Struct('Elf_Hash',
|
||||
self.Elf_word('nbuckets'),
|
||||
self.Elf_word('nchains'),
|
||||
Array(lambda ctx: ctx['nbuckets'], self.Elf_word('buckets')),
|
||||
Array(lambda ctx: ctx['nchains'], self.Elf_word('chains')))
|
||||
|
||||
def _create_gnu_hash(self) -> None:
|
||||
# Structure of the GNU-style hash table header. Documentation for this
|
||||
# table is mostly in the GLIBC source code, a good explanation of the
|
||||
# format can be found in this blog post:
|
||||
# https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/
|
||||
self.Gnu_Hash = Struct('Gnu_Hash',
|
||||
self.Elf_word('nbuckets'),
|
||||
self.Elf_word('symoffset'),
|
||||
self.Elf_word('bloom_size'),
|
||||
self.Elf_word('bloom_shift'),
|
||||
Array(lambda ctx: ctx['bloom_size'], self.Elf_xword('bloom')),
|
||||
Array(lambda ctx: ctx['nbuckets'], self.Elf_word('buckets')))
|
||||
|
||||
def _create_gnu_debuglink(self) -> None:
|
||||
self.Gnu_debuglink = Struct('Gnu_debuglink',
|
||||
CString("filename"),
|
||||
Padding(lambda ctx: 3 - len(ctx.filename) % 4, strict=True),
|
||||
self.Elf_word("checksum"))
|
||||
Reference in New Issue
Block a user