建立 MAG160C 逆向工程交接仓库

This commit is contained in:
ZXCLI
2026-08-11 19:08:44 +08:00
commit 8409b27ba3
3135 changed files with 534408 additions and 0 deletions
@@ -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)
+238
View File
@@ -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"))