Compare commits

..

6 Commits

Author SHA1 Message Date
Nicolas Kruse 7d2028c2bb blank line removed 2025-04-02 21:46:48 +02:00
Nicolas Kruse 27e48f8d5b .data_hex properties fixed 2025-04-02 21:46:18 +02:00
Nicolas Kruse da74692664 test extended for iterating section.symbols 2025-04-02 21:42:44 +02:00
Nicolas Kruse e2a6c1bcd4 changed functions without argument to property: symbol.relocations, .data, .data_hex 2025-04-02 21:42:06 +02:00
Nicolas Kruse 6b797baf0e test checking for empty results 2025-04-02 21:20:36 +02:00
Nicolas Kruse 85d911c5cb Docstring format updated, module description added 2025-04-02 21:20:04 +02:00
2 changed files with 70 additions and 90 deletions

View File

@ -1,3 +1,11 @@
"""Pelfy is an ELF parser for parsing header fields, sections, symbols and relocations.
Typical usage example:
elf = pelfy.open_elf_file('obj/test-c-riscv64-linux-gnu-gcc-12-O3.o')
print(elf.sections)
"""
from . import fields_data as fdat from . import fields_data as fdat
from . import output_formatter from . import output_formatter
from typing import TypeVar, Literal, Iterable, Generic, Iterator, Generator from typing import TypeVar, Literal, Iterable, Generic, Iterator, Generator
@ -21,34 +29,27 @@ def open_elf_file(file_path: str) -> 'elf_file':
class elf_symbol(): class elf_symbol():
"""A class for representing data of an ELF symbol """A class for representing data of an ELF symbol
Args:
file: ELF file object
fields: symbol header fields
index: Absolut index in the symbol table
Attributes: Attributes:
file: Points to the parent ELF file object. file: Points to the parent ELF file object.
name: Name of the symbol name: Name of the symbol
section: section where the symbol data is placed section: section where the symbol data is placed
index: Absolut index in the symbol table index: Absolut index in the symbol table
info: Type of the symbol info: Type of the symbol
description: Description of the symbol type description: Description of the symbol type
stb: visibility of the symbol (local, global, etc.) stb: visibility of the symbol (local, global, etc.)
stb_description: Description of the symbol visibility stb_description: Description of the symbol visibility
fields: All symbol header fields as dict fields: All symbol header fields as dict
""" """
def __init__(self, file: 'elf_file', fields: dict[str, int], index: int): def __init__(self, file: 'elf_file', fields: dict[str, int], index: int):
"""
Initializes ELF symbol instance
Args:
file: ELF file object
fields: symbol header fields
index: Absolut index in the symbol table
"""
self.fields = fields self.fields = fields
self.file = file self.file = file
@ -64,13 +65,11 @@ class elf_symbol():
self.info, self.description = fdat.st_info_values[fields['st_info'] & 0x0F] self.info, self.description = fdat.st_info_values[fields['st_info'] & 0x0F]
self.stb, self.stb_description = fdat.stb_values[fields['st_info'] >> 4] self.stb, self.stb_description = fdat.stb_values[fields['st_info'] >> 4]
def read_data(self) -> bytes: @property
def data(self) -> bytes:
"""Returns the binary data the symbol is pointing to. """Returns the binary data the symbol is pointing to.
The offset in the ELF file is calculated by: The offset in the ELF file is calculated by:
sections[symbol.st_shndx].sh_offset + symbol.st_value sections[symbol.st_shndx].sh_offset + symbol.st_value
Returns:
Symbol data
""" """
assert self.section, 'This symbol is not associated to a data section' assert self.section, 'This symbol is not associated to a data section'
if self.section.type == 'SHT_NOBITS': if self.section.type == 'SHT_NOBITS':
@ -79,17 +78,18 @@ class elf_symbol():
offset = self.section['sh_offset'] + self['st_value'] offset = self.section['sh_offset'] + self['st_value']
return self.file.read_bytes(offset, self['st_size']) return self.file.read_bytes(offset, self['st_size'])
def read_data_hex(self) -> str: @property
return ' '.join(f'{d:02X}' for d in self.read_data()) def data_hex(self) -> str:
"""Returns the binary data the symbol is pointing to as hex string.
"""
return ' '.join(f'{d:02X}' for d in self.data)
def get_relocations(self) -> 'relocation_list': @property
"""List all relocations that are pointing to this symbol. def relocations(self) -> 'relocation_list':
"""Relocations that are pointing to this symbol.
The symbol section must be of type SHT_PROGBITS (program code). Therefore The symbol section must be of type SHT_PROGBITS (program code). Therefore
this function lists typically all relocations that will be this property returns typically all relocations that will be
applied to the function represented by the symbol. applied to the function represented by the symbol.
Returns:
List of relocations
""" """
ret: list[elf_relocation] = list() ret: list[elf_relocation] = list()
assert self.section and self.section.type == 'SHT_PROGBITS' assert self.section and self.section.type == 'SHT_PROGBITS'
@ -118,29 +118,23 @@ class elf_symbol():
class elf_section(): class elf_section():
"""A class for representing data of an ELF section """A class for representing data of an ELF section
Args:
file: ELF file object
fields: Section header fields
name: Name of the section
index: Absolut index in the symbol table
Attributes: Attributes:
file: Points to the parent ELF file object. file: Points to the parent ELF file object.
name: Name of the section name: Name of the section
index: Absolut index of the section index: Absolut index of the section
type: Type of the section type: Type of the section
description: Description of the section type description: Description of the section type
fields: All symbol header fields as dict fields: All symbol header fields as dict
""" """
def __init__(self, file: 'elf_file', fields: dict[str, int], name: str, index: int): def __init__(self, file: 'elf_file', fields: dict[str, int], name: str, index: int):
"""Initializes an ELF section instance
Args:
file: ELF file object
fields: Section header fields
name: Name of the section
index: Absolut index in the symbol table
"""
self.fields = fields self.fields = fields
self.file = file self.file = file
self.index = index self.index = index
@ -156,29 +150,27 @@ class elf_section():
self.description = '' self.description = ''
self.type = str(hex(fields['sh_type'])) self.type = str(hex(fields['sh_type']))
def read_data(self) -> bytes: @property
def data(self) -> bytes:
"""Returns the binary data from the section. """Returns the binary data from the section.
The offset in the ELF file is given by: section.sh_offset The offset in the ELF file is given by: section.sh_offset
Returns:
Data of the section
""" """
if self.type == 'SHT_NOBITS': if self.type == 'SHT_NOBITS':
return b'\x00' * self['sh_size'] return b'\x00' * self['sh_size']
else: else:
return self.file.read_bytes(self['sh_offset'], self['sh_size']) return self.file.read_bytes(self['sh_offset'], self['sh_size'])
def get_symbols(self) -> 'symbol_list': @property
"""Lists all ELF symbols associated with this section def symbols(self) -> 'symbol_list':
"""All ELF symbols associated with this section
Returns:
Symbol list
""" """
return symbol_list(self.file._list_symbols(self.index)) return symbol_list(self.file._list_symbols(self.index))
def get_data_hex(self) -> str: @property
data = self.file.read_bytes(self['sh_offset'], self['sh_size']) def data_hex(self) -> str:
return ' '.join(f'{d:02X}' for d in data) """Returns the binary data from the section as hex string.
"""
return ' '.join(f'{d:02X}' for d in self.data)
def __getitem__(self, key: str | int) -> int: def __getitem__(self, key: str | int) -> int:
if isinstance(key, str): if isinstance(key, str):
@ -197,38 +189,28 @@ class elf_section():
class elf_relocation(): class elf_relocation():
"""A class for representing data of a relocation """A class for representing data of a relocation
Args:
file: ELF file object
fields: Relocation header fields
symbol_index: Index of the symbol to relocate in the symbol table
relocation_type: Type of the relocation (numeric)
sh_info: Index of the section this relocation applies to
index: Absolut index of the relocation in the associated relocation section
Attributes: Attributes:
file: Points to the parent ELF file object. file: Points to the parent ELF file object.
index: Absolut index of the relocation in the associated relocation section index: Absolut index of the relocation in the associated relocation section
symbol: Symbol to relocate symbol: Symbol to relocate
type: Type of the relocation type: Type of the relocation
calculation: Description of the relocation calculation calculation: Description of the relocation calculation
bits: number ob bits to patch by the relocation bits: number ob bits to patch by the relocation
target_section: Pointing to the section where this relocation applies to target_section: Pointing to the section where this relocation applies to
fields: All relocation header fields as dict fields: All relocation header fields as dict
""" """
def __init__(self, file: 'elf_file', fields: dict[str, int], symbol_index: int, def __init__(self, file: 'elf_file', fields: dict[str, int], symbol_index: int,
relocation_type: int, sh_info: int, index: int): relocation_type: int, sh_info: int, index: int):
"""Initializes a ELF relocation instance
Args:
file: ELF file object
fields: Relocation header fields
symbol_index: Index of the symbol to relocate in the symbol table
relocation_type: Type of the relocation (numeric)
sh_info: Index of the section this relocation applies to
index: Absolut index of the relocation in the associated relocation section
"""
self.fields = fields self.fields = fields
self.file = file self.file = file
self.index = index self.index = index
@ -365,37 +347,29 @@ class relocation_list(elf_list[elf_relocation]):
class elf_file: class elf_file:
"""A class for representing data of an ELF file in a structured form """A class for representing data of an ELF file in a structured form
Args:
data: binary ELF data
Attributes: Attributes:
byteorder: Byte order of the architecture 'little' or 'big' byteorder: Byte order of the architecture 'little' or 'big'
(based on e_ident[EI_DATA]) (based on e_ident[EI_DATA])
bit_width: Bit with of the architecture: 32 or 64 (based on bit_width: Bit with of the architecture: 32 or 64 (based on
e_ident[EI_CLASS]) e_ident[EI_CLASS])
architecture: Name of the architecture (based on e_machine) architecture: Name of the architecture (based on e_machine)
fields: All ELF header fields as dict fields: All ELF header fields as dict
sections: A list of all ELF sections sections: A list of all ELF sections
symbols: A list of all ELF symbols symbols: A list of all ELF symbols
functions: A list of all function symbols (STT_FUNC) functions: A list of all function symbols (STT_FUNC)
objects: A list of all variable/object symbols (STT_OBJECT) objects: A list of all variable/object symbols (STT_OBJECT)
code_relocations: A list of all code relocations (.rela.text and .rel.text) code_relocations: A list of all code relocations (.rela.text and .rel.text)
symbol_table_section: The symbol table section (first section with symbol_table_section: The symbol table section (first section with
the type SHT_SYMTAB) the type SHT_SYMTAB)
string_table_section: The string table section (first section with string_table_section: The string table section (first section with
the name .strtab) the name .strtab)
""" """
def __init__(self, data: bytes | bytearray): def __init__(self, data: bytes | bytearray):
"""Initializes an ELF file instance
Args:
data: binary ELF data
"""
assert isinstance(data, (bytes, bytearray)), 'Binary ELF data must be provided as bytes or bytearray.' assert isinstance(data, (bytes, bytearray)), 'Binary ELF data must be provided as bytes or bytearray.'
self._data = data self._data = data
@ -524,7 +498,6 @@ class elf_file:
Args: Args:
offset: Specify first byte relative to the start of offset: Specify first byte relative to the start of
the ELF file. the ELF file.
num_bytes: Specify the number of bytes to read. num_bytes: Specify the number of bytes to read.
Returns: Returns:
@ -539,9 +512,7 @@ class elf_file:
Args: Args:
offset: Specify first byte of the integer relative to offset: Specify first byte of the integer relative to
the start of the ELF file. the start of the ELF file.
num_bytes: Specify the size of the integer in bytes. num_bytes: Specify the size of the integer in bytes.
signed: Select if the integer is a signed integer. signed: Select if the integer is a signed integer.
Returns: Returns:
@ -558,7 +529,6 @@ class elf_file:
Args: Args:
offset: Specify first byte of the string relative to offset: Specify first byte of the string relative to
the start of the ELF file. the start of the ELF file.
encoding: Encoding used for text decoding. encoding: Encoding used for text decoding.
Returns: Returns:

View File

@ -19,12 +19,22 @@ def test_simple_c() -> None:
print(elf.code_relocations) print(elf.code_relocations)
print('\n') print('\n')
section_count = 0
assert elf.sections
for section in elf.sections: for section in elf.sections:
assert known_name(section.description), f"Section type {section.type} for {elf.architecture} in {path} is unknown." assert known_name(section.description), f"Section type {section.type} for {elf.architecture} in {path} is unknown."
for sym in section.symbols:
assert known_name(sym.info), f"Symbol info {sym.info} for {elf.architecture} in {path} is unknown."
section_count += 1
assert section_count > 2
assert elf.symbols
for sym in elf.symbols: for sym in elf.symbols:
assert known_name(sym.info), f"Symbol info {sym.info} for {elf.architecture} in {path} is unknown." assert known_name(sym.info), f"Symbol info {sym.info} for {elf.architecture} in {path} is unknown."
assert elf.get_relocations()
for reloc in elf.get_relocations(): for reloc in elf.get_relocations():
assert known_name(reloc.type), f"Relocation type {reloc.type} for {elf.architecture} in {path} is unknown." assert known_name(reloc.type), f"Relocation type {reloc.type} for {elf.architecture} in {path} is unknown."