mirror of https://github.com/Nonannet/pelfy.git
Compare commits
No commits in common. "main" and "v1.0.2" have entirely different histories.
|
@ -8,7 +8,6 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Linting, typechecking and testing code
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
@ -9,7 +9,7 @@ permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-and-deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -18,33 +18,20 @@ jobs:
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install .[doc_build]
|
run: pip install sphinx sphinx_rtd_theme sphinx-autodoc-typehints myst-parser
|
||||||
- name: Generate Examples
|
- name: Generate Class List
|
||||||
run: python ./docs/source/generate_class_list.py
|
run: |
|
||||||
|
pip install .
|
||||||
|
python ./docs/source/generate_class_list.py
|
||||||
- name: Build Docs
|
- name: Build Docs
|
||||||
run: |
|
run: |
|
||||||
cp LICENSE docs/source/LICENSE.md
|
|
||||||
cd docs
|
cd docs
|
||||||
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
sphinx-apidoc -o ./source/ ../src/ -M --no-toc
|
||||||
rm ./source/*.rst
|
rm ./source/*.rst
|
||||||
make html
|
make html
|
||||||
touch ./build/html/.nojekyll
|
touch ./build/html/.nojekyll
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: docs/build/html
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
uses: actions/deploy-pages@v4
|
with:
|
||||||
|
branch: gh-pages
|
||||||
|
folder: docs/build/html
|
||||||
|
|
|
@ -9,9 +9,6 @@ jobs:
|
||||||
publish:
|
publish:
|
||||||
name: Build and publish
|
name: Build and publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment:
|
|
||||||
name: pypi
|
|
||||||
url: https://pypi.org/project/${{ github.event.repository.name }}/
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -20,7 +20,6 @@ share/python-wheels/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
.vscode
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
@ -49,4 +48,4 @@ dmypy.json
|
||||||
|
|
||||||
# docs
|
# docs
|
||||||
docs/build/
|
docs/build/
|
||||||
docs/source/api/
|
docs/source/modules.md
|
||||||
|
|
|
@ -10,7 +10,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, os.path.abspath("../src/"))
|
sys.path.insert(0, os.path.abspath("../src/"))
|
||||||
|
|
||||||
project = 'pelfy'
|
project = 'pyhoff'
|
||||||
copyright = '2025, Nicolas Kruse'
|
copyright = '2025, Nicolas Kruse'
|
||||||
author = 'Nicolas Kruse'
|
author = 'Nicolas Kruse'
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ exclude_patterns = []
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
# html_theme = 'alabaster'
|
# html_theme = 'alabaster'
|
||||||
html_theme = 'pydata_sphinx_theme'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
autodoc_inherit_docstrings = True
|
autodoc_inherit_docstrings = True
|
||||||
|
|
|
@ -1,86 +1,60 @@
|
||||||
# This script generates the source md-files for all classes and functions for the docs
|
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import inspect
|
import inspect
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from io import TextIOWrapper
|
from io import TextIOWrapper
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def write_manual(f: TextIOWrapper, doc_files: list[str], title: str) -> None:
|
|
||||||
write_dochtree(f, title, doc_files)
|
|
||||||
|
|
||||||
|
|
||||||
def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
def write_classes(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
||||||
"""Write the classes to the file."""
|
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
name for name, obj in inspect.getmembers(module, inspect.isclass)
|
name for name, obj in inspect.getmembers(module, inspect.isclass)
|
||||||
if (any(fnmatch.fnmatch(name, pat) for pat in patterns if name not in exclude) and
|
if (obj.__module__ == module_name and
|
||||||
|
any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude) and
|
||||||
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
|
obj.__doc__ and '(Automatic generated stub)' not in obj.__doc__)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
"""Write the classes to the file."""
|
||||||
|
f.write(f'## {title}\n\n')
|
||||||
if description:
|
if description:
|
||||||
f.write(f'{description}\n\n')
|
f.write(f'{description}\n\n')
|
||||||
|
|
||||||
write_dochtree(f, title, classes)
|
|
||||||
|
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
with open(f'docs/source/api/{cls}.md', 'w') as f2:
|
f.write('```{eval-rst}\n')
|
||||||
f2.write(f'# {module_name}.{cls}\n')
|
f.write(f'.. autoclass:: {module_name}.{cls}\n')
|
||||||
f2.write('```{eval-rst}\n')
|
f.write(' :members:\n')
|
||||||
f2.write(f'.. autoclass:: {module_name}.{cls}\n')
|
f.write(' :undoc-members:\n')
|
||||||
f2.write(' :members:\n')
|
f.write(' :show-inheritance:\n')
|
||||||
f2.write(' :undoc-members:\n')
|
f.write(' :inherited-members:\n')
|
||||||
f2.write(' :show-inheritance:\n')
|
if title != 'Base classes':
|
||||||
f2.write(' :inherited-members:\n')
|
f.write(' :exclude-members: select\n')
|
||||||
f2.write('```\n\n')
|
|
||||||
|
|
||||||
|
|
||||||
def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
|
||||||
"""Write the classes to the file."""
|
|
||||||
module = importlib.import_module(module_name)
|
|
||||||
|
|
||||||
functions = [
|
|
||||||
name for name, obj in inspect.getmembers(module, inspect.isfunction)
|
|
||||||
if (any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude))
|
|
||||||
]
|
|
||||||
|
|
||||||
if description:
|
|
||||||
f.write(f'{description}\n\n')
|
|
||||||
|
|
||||||
write_dochtree(f, title, functions)
|
|
||||||
|
|
||||||
for func in functions:
|
|
||||||
if not func.startswith('_'):
|
|
||||||
with open(f'docs/source/api/{func}.md', 'w') as f2:
|
|
||||||
f2.write(f'# {module_name}.{func}\n')
|
|
||||||
f2.write('```{eval-rst}\n')
|
|
||||||
f2.write(f'.. autofunction:: {module_name}.{func}\n')
|
|
||||||
f2.write('```\n\n')
|
|
||||||
|
|
||||||
|
|
||||||
def write_dochtree(f: TextIOWrapper, title: str, items: list[str]):
|
|
||||||
f.write('```{toctree}\n')
|
|
||||||
f.write(':maxdepth: 1\n')
|
|
||||||
f.write(f':caption: {title}:\n')
|
|
||||||
# f.write(':hidden:\n')
|
|
||||||
for text in items:
|
|
||||||
if not text.startswith('_'):
|
|
||||||
f.write(f"{text}\n")
|
|
||||||
f.write('```\n\n')
|
f.write('```\n\n')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def write_functions(f: TextIOWrapper, patterns: list[str], module_name: str, title: str, description: str = '', exclude: list[str] = []) -> None:
|
||||||
# Ensure the output directory exists
|
|
||||||
os.makedirs('docs/source/api', exist_ok=True)
|
|
||||||
|
|
||||||
with open('docs/source/api/index.md', 'w') as f:
|
module = importlib.import_module(module_name)
|
||||||
f.write('# Classes and functions\n\n')
|
|
||||||
|
|
||||||
|
classes = [
|
||||||
|
name for name, obj in inspect.getmembers(module, inspect.isfunction)
|
||||||
|
if (obj.__module__ == module_name and
|
||||||
|
any(fnmatch.fnmatch(name, pat) for pat in patterns if pat not in exclude))
|
||||||
|
]
|
||||||
|
|
||||||
|
"""Write the classes to the file."""
|
||||||
|
f.write(f'## {title}\n\n')
|
||||||
|
if description:
|
||||||
|
f.write(f'{description}\n\n')
|
||||||
|
|
||||||
|
for func in classes:
|
||||||
|
f.write('```{eval-rst}\n')
|
||||||
|
f.write(f'.. autofunction:: {module_name}.{func}\n')
|
||||||
|
f.write('```\n\n')
|
||||||
|
|
||||||
|
|
||||||
|
with open('docs/source/modules.md', 'w') as f:
|
||||||
|
f.write('# Functions and classes\n\n')
|
||||||
write_functions(f, ['*'], 'pelfy', title='Functions')
|
write_functions(f, ['*'], 'pelfy', title='Functions')
|
||||||
|
write_classes(f, ['elf_*'], 'pelfy', title='ELF classes')
|
||||||
write_classes(f, ['*'], 'pelfy', title='ELF Classes', exclude=['elf_list', 'relocation_list', 'section_list', 'symbol_list'],)
|
write_classes(f, ['*_list'], 'pelfy', title='Lists of ELF classes')
|
||||||
|
|
||||||
write_classes(f, ['*_list'], 'pelfy', title='ELF Lists')
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
```{toctree}
|
```{toctree}
|
||||||
:maxdepth: 1
|
:maxdepth: 2
|
||||||
:hidden:
|
:caption: Contents:
|
||||||
api/index
|
|
||||||
repo
|
readme
|
||||||
|
modules
|
||||||
```
|
```
|
||||||
|
|
||||||
```{include} ../../README.md
|
```{include} ../../README.md
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
```{include} ../../README.md
|
||||||
|
```
|
|
@ -1,3 +0,0 @@
|
||||||
# Code repository
|
|
||||||
|
|
||||||
Code repository is on GitHub: [github.com/Nonannet/pelfy](https://github.com/Nonannet/pelfy).
|
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "pelfy"
|
name = "pelfy"
|
||||||
version = "1.0.3"
|
version = "1.0.2"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
|
{ name="Nicolas Kruse", email="nicolas.kruse@nonan.net" },
|
||||||
]
|
]
|
||||||
|
@ -28,12 +28,6 @@ where = ["src"]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest", "flake8", "mypy"
|
"pytest", "flake8", "mypy"
|
||||||
]
|
]
|
||||||
doc_build = [
|
|
||||||
"sphinx",
|
|
||||||
"pydata_sphinx_theme",
|
|
||||||
"sphinx-autodoc-typehints",
|
|
||||||
"myst-parser"
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
files = ["src", "tests"]
|
files = ["src", "tests"]
|
||||||
|
|
|
@ -1,15 +1,564 @@
|
||||||
"""
|
"""Pelfy is an ELF parser for parsing header fields, sections, symbols and relocations.
|
||||||
pelfy is an ELF parser written in python.
|
|
||||||
|
|
||||||
Example usage:
|
Typical usage example:
|
||||||
>>> import pelfy
|
|
||||||
>>> elf = pelfy.open_elf_file('tests/obj/test-c-riscv64-linux-gnu-gcc-12-O3.o')
|
elf = pelfy.open_elf_file('obj/test-c-riscv64-linux-gnu-gcc-12-O3.o')
|
||||||
>>> elf.sections
|
print(elf.sections)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from ._main import open_elf_file, elf_symbol, elf_section, \
|
from . import fields_data as fdat
|
||||||
elf_relocation, elf_list, section_list, symbol_list, relocation_list, elf_file
|
from . import output_formatter
|
||||||
|
from typing import TypeVar, Literal, Iterable, Generic, Iterator, Generator, Optional, Union
|
||||||
|
|
||||||
__all__ = [
|
_T = TypeVar('_T')
|
||||||
'open_elf_file', 'elf_symbol', 'elf_section', 'elf_relocation',
|
|
||||||
'elf_list', 'section_list', 'symbol_list', 'relocation_list', 'elf_file']
|
|
||||||
|
def open_elf_file(file_path: str) -> 'elf_file':
|
||||||
|
"""Reads ELF data from file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: path of the ELF file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
elf_file object
|
||||||
|
"""
|
||||||
|
with open(file_path, mode='rb') as f:
|
||||||
|
return elf_file(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
class elf_symbol():
|
||||||
|
"""A class for representing data of an ELF symbol
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
file: Points to the parent ELF file object.
|
||||||
|
name: Name of the symbol
|
||||||
|
section: section where the symbol data is placed
|
||||||
|
index: Absolut index in the symbol table
|
||||||
|
info: Type of the symbol
|
||||||
|
description: Description of the symbol type
|
||||||
|
stb: visibility of the symbol (local, global, etc.)
|
||||||
|
stb_description: Description of the symbol visibility
|
||||||
|
fields: All symbol header fields as dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
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.file = file
|
||||||
|
|
||||||
|
if file.string_table_section:
|
||||||
|
self.name = file.read_string(file.string_table_section['sh_offset'] + fields['st_name'])
|
||||||
|
else:
|
||||||
|
self.name = ''
|
||||||
|
|
||||||
|
self.section: Optional[elf_section] = self.file.sections[self['st_shndx']] if self['st_shndx'] < len(self.file.sections) else None
|
||||||
|
|
||||||
|
self.index = index
|
||||||
|
|
||||||
|
self.info, self.description = fdat.st_info_values[fields['st_info'] & 0x0F]
|
||||||
|
self.stb, self.stb_description = fdat.stb_values[fields['st_info'] >> 4]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> bytes:
|
||||||
|
"""Returns the binary data the symbol is pointing to.
|
||||||
|
The offset in the ELF file is calculated by:
|
||||||
|
sections[symbol.st_shndx].sh_offset + symbol.st_value
|
||||||
|
"""
|
||||||
|
assert self.section, 'This symbol is not associated to a data section'
|
||||||
|
if self.section.type == 'SHT_NOBITS':
|
||||||
|
return b'\x00' * self['st_size']
|
||||||
|
else:
|
||||||
|
offset = self.section['sh_offset'] + self['st_value']
|
||||||
|
return self.file.read_bytes(offset, self['st_size'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
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)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relocations(self) -> 'relocation_list':
|
||||||
|
"""Relocations that are pointing to this symbol.
|
||||||
|
The symbol section must be of type SHT_PROGBITS (program code). Therefore
|
||||||
|
this property returns typically all relocations that will be
|
||||||
|
applied to the function represented by the symbol.
|
||||||
|
"""
|
||||||
|
ret: list[elf_relocation] = list()
|
||||||
|
assert self.section and self.section.type == 'SHT_PROGBITS'
|
||||||
|
for reloc in self.file.get_relocations():
|
||||||
|
if reloc.target_section == self.section:
|
||||||
|
offset = reloc['r_offset'] - self['st_value']
|
||||||
|
if 0 <= offset < self['st_size']:
|
||||||
|
ret.append(reloc)
|
||||||
|
return relocation_list(ret)
|
||||||
|
|
||||||
|
def __getitem__(self, key: Union[str, int]) -> int:
|
||||||
|
if isinstance(key, str):
|
||||||
|
assert key in self.fields, f'Unknown field name: {key}'
|
||||||
|
return self.fields[key]
|
||||||
|
else:
|
||||||
|
return list(self.fields.values())[key]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'index {self.index}\n' +\
|
||||||
|
f'name {self.name}\n' +\
|
||||||
|
f'stb {self.stb} ({self.stb_description})\n' +\
|
||||||
|
f'info {self.info} ({self.description})\n' +\
|
||||||
|
'\n'.join(f"{k:18} {v:4} {fdat.symbol_fields[k]}" for k, v in self.fields.items()) + '\n'
|
||||||
|
|
||||||
|
|
||||||
|
class elf_section():
|
||||||
|
"""A class for representing data of an ELF section
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
file: Points to the parent ELF file object.
|
||||||
|
name: Name of the section
|
||||||
|
index: Absolut index of the section
|
||||||
|
type: Type of the section
|
||||||
|
description: Description of the section type
|
||||||
|
fields: All symbol header fields as dict
|
||||||
|
"""
|
||||||
|
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.file = file
|
||||||
|
self.index = index
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
if fields['sh_type'] > 0x60000000:
|
||||||
|
# Range for OS, compiler and application specific types
|
||||||
|
self.description = [v for k, v in fdat.section_header_types_ex.items() if k >= fields['sh_type']][0]
|
||||||
|
self.type = str(hex(fields['sh_type']))
|
||||||
|
elif fields['sh_type'] in fdat.section_header_types:
|
||||||
|
self.type, self.description = fdat.section_header_types[fields['sh_type']]
|
||||||
|
else:
|
||||||
|
self.description = ''
|
||||||
|
self.type = str(hex(fields['sh_type']))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data(self) -> bytes:
|
||||||
|
"""Returns the binary data from the section.
|
||||||
|
The offset in the ELF file is given by: section.sh_offset
|
||||||
|
"""
|
||||||
|
if self.type == 'SHT_NOBITS':
|
||||||
|
return b'\x00' * self['sh_size']
|
||||||
|
else:
|
||||||
|
return self.file.read_bytes(self['sh_offset'], self['sh_size'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def symbols(self) -> 'symbol_list':
|
||||||
|
"""All ELF symbols associated with this section
|
||||||
|
"""
|
||||||
|
return symbol_list(self.file._list_symbols(self.index))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data_hex(self) -> str:
|
||||||
|
"""Returns the binary data from the section as hex string.
|
||||||
|
"""
|
||||||
|
return ' '.join(f'{d:02X}' for d in self.data)
|
||||||
|
|
||||||
|
def __getitem__(self, key: Union[str, int]) -> int:
|
||||||
|
if isinstance(key, str):
|
||||||
|
assert key in self.fields, f'Unknown field name: {key}'
|
||||||
|
return self.fields[key]
|
||||||
|
else:
|
||||||
|
return list(self.fields.values())[key]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'index {self.index}\n' +\
|
||||||
|
f'name {self.name}\n' +\
|
||||||
|
f'type {self.type} ({self.description})\n' +\
|
||||||
|
'\n'.join(f"{k:18} {v:4} {fdat.section_header[k]['description']}" for k, v in self.fields.items()) + '\n'
|
||||||
|
|
||||||
|
|
||||||
|
class elf_relocation():
|
||||||
|
"""A class for representing data of a relocation
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
file: Points to the parent ELF file object.
|
||||||
|
index: Absolut index of the relocation in the associated relocation section
|
||||||
|
symbol: Symbol to relocate
|
||||||
|
type: Type of the relocation
|
||||||
|
calculation: Description of the relocation calculation
|
||||||
|
bits: number ob bits to patch by the relocation
|
||||||
|
target_section: Pointing to the section where this relocation applies to
|
||||||
|
fields: All relocation header fields as dict
|
||||||
|
"""
|
||||||
|
def __init__(self, file: 'elf_file', fields: dict[str, int], symbol_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.file = file
|
||||||
|
self.index = index
|
||||||
|
self.symbol = file.symbols[symbol_index]
|
||||||
|
reloc_types = fdat.relocation_table_types.get(file.architecture)
|
||||||
|
if reloc_types and relocation_type in reloc_types:
|
||||||
|
self.type = reloc_types[relocation_type][0]
|
||||||
|
self.bits = reloc_types[relocation_type][1]
|
||||||
|
self.calculation = reloc_types[relocation_type][2]
|
||||||
|
else:
|
||||||
|
self.type = str(relocation_type)
|
||||||
|
self.bits = 0
|
||||||
|
self.calculation = ''
|
||||||
|
self.target_section: elf_section = file.sections[sh_info]
|
||||||
|
|
||||||
|
def __getitem__(self, key: Union[str, int]) -> int:
|
||||||
|
if isinstance(key, str):
|
||||||
|
assert key in self.fields, f'Unknown field name: {key}'
|
||||||
|
return self.fields[key]
|
||||||
|
else:
|
||||||
|
return list(self.fields.values())[key]
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'index {self.symbol.index}\n' +\
|
||||||
|
f'symbol {self.symbol.name}\n' +\
|
||||||
|
f'relocation type {self.type}\n' +\
|
||||||
|
f'calculation {self.calculation}\n' +\
|
||||||
|
f'bits {self.bits}\n' +\
|
||||||
|
'\n'.join(f'{k:18} {v:4}' for k, v in self.fields.items()) + '\n'
|
||||||
|
|
||||||
|
|
||||||
|
class elf_list(Generic[_T]):
|
||||||
|
"""A generic class for representing a list of ELF data items
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Iterable of ELF data items
|
||||||
|
"""
|
||||||
|
def __init__(self, data: Iterable[_T]):
|
||||||
|
self._data = list(data)
|
||||||
|
|
||||||
|
def __getitem__(self, key: Union[str, int]) -> _T:
|
||||||
|
if isinstance(key, str):
|
||||||
|
elements = [el for el in self._data if getattr(el, 'name', '') == key]
|
||||||
|
assert elements, f'Unknown name: {key}'
|
||||||
|
return elements[0]
|
||||||
|
else:
|
||||||
|
return self._data.__getitem__(key)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[_T]:
|
||||||
|
return iter(self._data)
|
||||||
|
|
||||||
|
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
||||||
|
return [], [[]], []
|
||||||
|
|
||||||
|
def _repr_table(self, format: output_formatter.table_format, raw_data: bool = False) -> str:
|
||||||
|
if raw_data and len(self):
|
||||||
|
table_dict: list[dict[str, int]] = [el.__dict__.get('fields', {' ': 0}) for el in self]
|
||||||
|
columns = list(table_dict[0].keys())
|
||||||
|
data: list[list[Union[str, int]]] = [list(el.values()) for el in table_dict]
|
||||||
|
radj = columns
|
||||||
|
else:
|
||||||
|
columns, data, radj = self._compact_table()
|
||||||
|
return output_formatter.generate_table(data, columns, right_adj_col=radj, format=format)
|
||||||
|
|
||||||
|
def to_dict_list(self) -> list[dict[str, Union[str, int]]]:
|
||||||
|
"""Exporting the ELF item data table to a list of dicts. It can be used with pandas:
|
||||||
|
df = pandas.DataFrame(elements.to_dict_list())
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Table data
|
||||||
|
"""
|
||||||
|
columns, data, _ = self._compact_table()
|
||||||
|
return [{k: v for k, v in zip(columns, row)} for row in data]
|
||||||
|
|
||||||
|
def to_html(self) -> str:
|
||||||
|
"""Exporting the ELF item data table to HTML.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML table
|
||||||
|
"""
|
||||||
|
return self._repr_table('html')
|
||||||
|
|
||||||
|
def to_markdown(self) -> str:
|
||||||
|
"""Exporting the ELF item data table to markdown.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Markdown table
|
||||||
|
"""
|
||||||
|
return self._repr_table('markdown')
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self._repr_table('text')
|
||||||
|
|
||||||
|
def _repr_html_(self) -> str:
|
||||||
|
return self._repr_table('html')
|
||||||
|
|
||||||
|
def _repr_markdown_(self) -> str:
|
||||||
|
return self._repr_table('markdown')
|
||||||
|
|
||||||
|
|
||||||
|
class section_list(elf_list[elf_section]):
|
||||||
|
"""A class for representing a list of ELF section
|
||||||
|
"""
|
||||||
|
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
||||||
|
columns = ['index', 'name', 'type', 'description']
|
||||||
|
data: list[list[Union[str, int]]] = [[item.index, item.name, item.type,
|
||||||
|
item.description] for item in self]
|
||||||
|
return columns, data, ['index']
|
||||||
|
|
||||||
|
|
||||||
|
class symbol_list(elf_list[elf_symbol]):
|
||||||
|
"""A class for representing a list of ELF symbols
|
||||||
|
"""
|
||||||
|
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
||||||
|
columns = ['index', 'name', 'info', 'size', 'stb', 'section', 'description']
|
||||||
|
data: list[list[Union[str, int]]] = [[item.index, item.name, item.info, item.fields['st_size'],
|
||||||
|
item.stb, item.section.name if item.section else '', item.description] for item in self]
|
||||||
|
return columns, data, ['index', 'size']
|
||||||
|
|
||||||
|
|
||||||
|
class relocation_list(elf_list[elf_relocation]):
|
||||||
|
"""A class for representing a list of ELF relocations
|
||||||
|
"""
|
||||||
|
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
||||||
|
columns = ['index', 'symbol name', 'type', 'calculation', 'bits']
|
||||||
|
data: list[list[Union[str, int]]] = [[item.index, item.symbol.name, item.type,
|
||||||
|
item.calculation, item.bits] for item in self]
|
||||||
|
return columns, data, ['index', 'bits']
|
||||||
|
|
||||||
|
|
||||||
|
class elf_file:
|
||||||
|
"""A class for representing data of an ELF file in a structured form
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
byteorder: Byte order of the architecture 'little' or 'big'
|
||||||
|
(based on e_ident[EI_DATA])
|
||||||
|
bit_width: Bit with of the architecture: 32 or 64 (based on
|
||||||
|
e_ident[EI_CLASS])
|
||||||
|
architecture: Name of the architecture (based on e_machine)
|
||||||
|
fields: All ELF header fields as dict
|
||||||
|
sections: A list of all ELF sections
|
||||||
|
symbols: A list of all ELF symbols
|
||||||
|
functions: A list of all function symbols (STT_FUNC)
|
||||||
|
objects: A list of all variable/object symbols (STT_OBJECT)
|
||||||
|
code_relocations: A list of all code relocations (.rela.text and .rel.text)
|
||||||
|
symbol_table_section: The symbol table section (first section with
|
||||||
|
the type SHT_SYMTAB)
|
||||||
|
string_table_section: The string table section (first section with
|
||||||
|
the name .strtab)
|
||||||
|
"""
|
||||||
|
def __init__(self, data: Union[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.'
|
||||||
|
self._data = data
|
||||||
|
|
||||||
|
# Defaults required for function _read_int_from_elf_field
|
||||||
|
self.bit_width = 32
|
||||||
|
self.byteorder: Literal['little', 'big'] = 'little'
|
||||||
|
|
||||||
|
assert self._read_bytes_from_elf_field('e_ident[EI_MAG]') == b'\x7fELF', 'Not an ELF file'
|
||||||
|
|
||||||
|
self.bit_width = {1: 32, 2: 64}[self._read_int_from_elf_field('e_ident[EI_CLASS]')]
|
||||||
|
|
||||||
|
byte_order = self._read_int_from_elf_field('e_ident[EI_DATA]')
|
||||||
|
assert byte_order in [1, 2], 'Invalid byte order value e_ident[EI_DATA]'
|
||||||
|
self.byteorder = 'little' if byte_order == 1 else 'big'
|
||||||
|
|
||||||
|
self.fields = {fn: self._read_int_from_elf_field(fn) for fn in fdat.elf_header_field.keys()}
|
||||||
|
|
||||||
|
arch_entr = fdat.e_machine_dict.get(self.fields['e_machine'])
|
||||||
|
self.architecture = arch_entr[0] if arch_entr else str(self.fields['e_machine'])
|
||||||
|
|
||||||
|
section_data = list(self._list_sections())
|
||||||
|
name_addr: dict[str, int] = section_data[self.fields['e_shstrndx']] if section_data else dict()
|
||||||
|
section_names = (self.read_string(name_addr['sh_offset'] + f['sh_name']) for f in section_data)
|
||||||
|
|
||||||
|
self.sections = section_list(elf_section(self, sd, sn, i)
|
||||||
|
for i, (sd, sn) in enumerate(zip(section_data, section_names)))
|
||||||
|
|
||||||
|
ret_sections = [sh for sh in self.sections if sh.type == 'SHT_SYMTAB']
|
||||||
|
self.symbol_table_section = ret_sections[0] if ret_sections else None
|
||||||
|
|
||||||
|
ret_sections = [sh for sh in self.sections if sh.name == '.strtab']
|
||||||
|
self.string_table_section = ret_sections[0] if ret_sections else None
|
||||||
|
|
||||||
|
self.symbols = symbol_list(self._list_symbols())
|
||||||
|
|
||||||
|
self.functions = symbol_list(s for s in self.symbols if s.info == 'STT_FUNC')
|
||||||
|
self.objects = symbol_list(s for s in self.symbols if s.info == 'STT_OBJECT')
|
||||||
|
|
||||||
|
self.code_relocations = self.get_relocations(['.rela.text', '.rel.text'])
|
||||||
|
|
||||||
|
def _list_sections(self) -> Generator[dict[str, int], None, None]:
|
||||||
|
for i in range(self.fields['e_shnum']):
|
||||||
|
offs = self.fields['e_shoff'] + i * self.fields['e_shentsize']
|
||||||
|
yield {fn: self._read_from_sh_field(offs, fn) for fn in fdat.section_header.keys()}
|
||||||
|
|
||||||
|
def _list_symbols(self, section_index: Optional[int] = None) -> Generator[elf_symbol, None, None]:
|
||||||
|
if self.symbol_table_section:
|
||||||
|
offs = self.symbol_table_section['sh_offset']
|
||||||
|
|
||||||
|
for j, i in enumerate(range(offs, self.symbol_table_section['sh_size'] + offs, self.symbol_table_section['sh_entsize'])):
|
||||||
|
ret = {'st_name': self.read_int(i, 4)}
|
||||||
|
|
||||||
|
if self.bit_width == 32:
|
||||||
|
ret['st_value'] = self.read_int(i + 4, 4)
|
||||||
|
ret['st_size'] = self.read_int(i + 8, 4)
|
||||||
|
ret['st_info'] = self.read_int(i + 12, 1)
|
||||||
|
ret['st_other'] = self.read_int(i + 13, 1)
|
||||||
|
ret['st_shndx'] = self.read_int(i + 14, 2)
|
||||||
|
elif self.bit_width == 64:
|
||||||
|
ret['st_info'] = self.read_int(i + 4, 1)
|
||||||
|
ret['st_other'] = self.read_int(i + 5, 1)
|
||||||
|
ret['st_shndx'] = self.read_int(i + 6, 2)
|
||||||
|
ret['st_value'] = self.read_int(i + 8, 8)
|
||||||
|
ret['st_size'] = self.read_int(i + 16, 8)
|
||||||
|
|
||||||
|
if section_index is None or section_index == ret['st_shndx']:
|
||||||
|
yield elf_symbol(self, ret, j)
|
||||||
|
|
||||||
|
def get_relocations(self, reloc_section: Optional[Union[elf_section, str, list[str]]] = None) -> relocation_list:
|
||||||
|
"""List relocations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reloc_section: Specifies the relocation section from which the
|
||||||
|
relocations should be listed. It can be provided as
|
||||||
|
elf_section object or by its name. If not provided
|
||||||
|
(reloc_section=None) relocations from all relocation
|
||||||
|
sections are returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of relocations
|
||||||
|
"""
|
||||||
|
if isinstance(reloc_section, elf_section):
|
||||||
|
assert reloc_section.type in ('SHT_REL', 'SHT_RELA'), f'{reloc_section.name} is not a relocation section'
|
||||||
|
return relocation_list(self._list_relocations(reloc_section))
|
||||||
|
else:
|
||||||
|
relocations: list[elf_relocation] = list()
|
||||||
|
for sh in self.sections:
|
||||||
|
if sh.type in ('SHT_REL', 'SHT_RELA'):
|
||||||
|
if reloc_section is None or \
|
||||||
|
(isinstance(reloc_section, str) and sh.name == reloc_section) or \
|
||||||
|
(isinstance(reloc_section, list) and sh.name in reloc_section):
|
||||||
|
relocations += relocation_list(self._list_relocations(sh))
|
||||||
|
|
||||||
|
return relocation_list(relocations)
|
||||||
|
|
||||||
|
def _list_relocations(self, sh: elf_section) -> Generator[elf_relocation, None, None]:
|
||||||
|
"""List relocations for a elf_section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
elf_section: Specifies the relocation section from which the
|
||||||
|
relocations should be listed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Relocations from specified elf_section
|
||||||
|
"""
|
||||||
|
offs = sh['sh_offset']
|
||||||
|
for i, el_off in enumerate(range(offs, sh['sh_size'] + offs, sh['sh_entsize'])):
|
||||||
|
ret: dict[str, int] = dict()
|
||||||
|
|
||||||
|
if self.bit_width == 32:
|
||||||
|
ret['r_offset'] = self.read_int(el_off, 4)
|
||||||
|
r_info = self.read_int(el_off + 4, 4)
|
||||||
|
ret['r_info'] = r_info
|
||||||
|
ret['r_addend'] = self.read_int(el_off + 8, 4, True) if sh.type == 'SHT_RELA' else 0
|
||||||
|
yield elf_relocation(self, ret, r_info >> 8, r_info & 0xFF, sh['sh_info'], i)
|
||||||
|
elif self.bit_width == 64:
|
||||||
|
ret['r_offset'] = self.read_int(el_off, 8)
|
||||||
|
r_info = self.read_int(el_off + 8, 8)
|
||||||
|
ret['r_info'] = r_info
|
||||||
|
ret['r_addend'] = self.read_int(el_off + 16, 8, True) if sh.type == 'SHT_RELA' else 0
|
||||||
|
yield elf_relocation(self, ret, r_info >> 32, r_info & 0xFFFFFFFF, sh['sh_info'], i)
|
||||||
|
|
||||||
|
def read_bytes(self, offset: int, num_bytes: int) -> bytes:
|
||||||
|
"""Read bytes from ELF file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset: Specify first byte relative to the start of
|
||||||
|
the ELF file.
|
||||||
|
num_bytes: Specify the number of bytes to read.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Binary data as bytes
|
||||||
|
"""
|
||||||
|
return self._data[offset:offset + num_bytes]
|
||||||
|
|
||||||
|
def read_int(self, offset: int, num_bytes: int, signed: bool = False) -> int:
|
||||||
|
"""Read an integer from the ELF file. Byte order is
|
||||||
|
selected according to the architecture (e_ident[EI_DATA]).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset: Specify first byte of the integer relative to
|
||||||
|
the start of the ELF file.
|
||||||
|
num_bytes: Specify the size of the integer in bytes.
|
||||||
|
signed: Select if the integer is a signed integer.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Integer value
|
||||||
|
"""
|
||||||
|
return int.from_bytes(self._data[offset:offset + num_bytes], self.byteorder, signed=signed)
|
||||||
|
|
||||||
|
# def int_to_bytes(self, value: int, num_bytes: int = 4, signed: bool = False) -> int:
|
||||||
|
# return value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=signed)
|
||||||
|
|
||||||
|
def read_string(self, offset: int, encoding: str = 'utf-8') -> str:
|
||||||
|
"""Read a zero-terminated text string from the ELF file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
offset: Specify first byte of the string relative to
|
||||||
|
the start of the ELF file.
|
||||||
|
encoding: Encoding used for text decoding.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Text string
|
||||||
|
"""
|
||||||
|
str_end = self._data.find(b'\x00', offset)
|
||||||
|
return self._data[offset:str_end].decode(encoding)
|
||||||
|
|
||||||
|
def _read_int_from_elf_field(self, field_name: str) -> int:
|
||||||
|
field = fdat.elf_header_field[field_name]
|
||||||
|
offs = int(field[str(self.bit_width)], base=16)
|
||||||
|
byte_len = int(field['size' + str(self.bit_width)])
|
||||||
|
return self.read_int(offs, byte_len)
|
||||||
|
|
||||||
|
def _read_bytes_from_elf_field(self, field_name: str) -> bytes:
|
||||||
|
field = fdat.elf_header_field[field_name]
|
||||||
|
offs = int(field[str(self.bit_width)], base=16)
|
||||||
|
byte_len = int(field['size' + str(self.bit_width)])
|
||||||
|
return self.read_bytes(offs, byte_len)
|
||||||
|
|
||||||
|
def _read_from_sh_field(self, offset: int, field_name: str) -> int:
|
||||||
|
field = fdat.section_header[field_name]
|
||||||
|
offs = int(field[str(self.bit_width)], base=16) + offset
|
||||||
|
byte_len = int(field['size' + str(self.bit_width)])
|
||||||
|
return self.read_int(offs, byte_len)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
hf_list = ((hf, self.fields[hf['field_name']]) for hf in fdat.elf_header_field.values())
|
||||||
|
return '\n'.join(f"{hf['field_name']:24} {v:4} {hf['description']}" for hf, v in hf_list) + '\n'
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> int:
|
||||||
|
assert key in self.fields, f'Unknown field name: {key}'
|
||||||
|
return self.fields[key]
|
||||||
|
|
|
@ -1,572 +0,0 @@
|
||||||
"""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 _output_formatter
|
|
||||||
from typing import TypeVar, Literal, Iterable, Generic, Iterator, Generator, Optional, Union
|
|
||||||
|
|
||||||
_T = TypeVar('_T')
|
|
||||||
|
|
||||||
|
|
||||||
def open_elf_file(file_path: str) -> 'elf_file':
|
|
||||||
"""Reads ELF data from file
|
|
||||||
|
|
||||||
Args:
|
|
||||||
file_path: path of the ELF file
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
elf_file object
|
|
||||||
"""
|
|
||||||
with open(file_path, mode='rb') as f:
|
|
||||||
return elf_file(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
class elf_symbol():
|
|
||||||
"""A class for representing data of an ELF symbol
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
file: Points to the parent ELF file object.
|
|
||||||
name: Name of the symbol
|
|
||||||
section: section where the symbol data is placed
|
|
||||||
index: Absolut index in the symbol table
|
|
||||||
info: Type of the symbol
|
|
||||||
description: Description of the symbol type
|
|
||||||
stb: visibility of the symbol (local, global, etc.)
|
|
||||||
stb_description: Description of the symbol visibility
|
|
||||||
fields: All symbol header fields as dict
|
|
||||||
"""
|
|
||||||
|
|
||||||
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.file = file
|
|
||||||
|
|
||||||
if file.string_table_section:
|
|
||||||
self.name = file.read_string(file.string_table_section['sh_offset'] + fields['st_name'])
|
|
||||||
else:
|
|
||||||
self.name = ''
|
|
||||||
|
|
||||||
self.section: Optional[elf_section] = self.file.sections[self['st_shndx']] if self['st_shndx'] < len(self.file.sections) else None
|
|
||||||
|
|
||||||
self.index = index
|
|
||||||
|
|
||||||
self.info, self.description = fdat.st_info_values[fields['st_info'] & 0x0F]
|
|
||||||
self.stb, self.stb_description = fdat.stb_values[fields['st_info'] >> 4]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data(self) -> bytes:
|
|
||||||
"""Returns the binary data the symbol is pointing to.
|
|
||||||
The offset in the ELF file is calculated by:
|
|
||||||
sections[symbol.st_shndx].sh_offset + symbol.st_value
|
|
||||||
"""
|
|
||||||
assert self.section, 'This symbol is not associated to a data section'
|
|
||||||
if self.section.type == 'SHT_NOBITS':
|
|
||||||
return b'\x00' * self['st_size']
|
|
||||||
else:
|
|
||||||
offset = self.section['sh_offset'] + self['st_value']
|
|
||||||
return self.file.read_bytes(offset, self['st_size'])
|
|
||||||
|
|
||||||
@property
|
|
||||||
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)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def relocations(self) -> 'relocation_list':
|
|
||||||
"""Relocations that are pointing to this symbol.
|
|
||||||
The symbol section must be of type SHT_PROGBITS (program code). Therefore
|
|
||||||
this property returns typically all relocations that will be
|
|
||||||
applied to the function represented by the symbol.
|
|
||||||
"""
|
|
||||||
ret: list[elf_relocation] = list()
|
|
||||||
assert self.section and self.section.type == 'SHT_PROGBITS'
|
|
||||||
for reloc in self.file.get_relocations():
|
|
||||||
if reloc.target_section == self.section:
|
|
||||||
offset = reloc['r_offset'] - self['st_value']
|
|
||||||
if 0 <= offset < self['st_size']:
|
|
||||||
ret.append(reloc)
|
|
||||||
return relocation_list(ret)
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[str, int]) -> int:
|
|
||||||
if isinstance(key, str):
|
|
||||||
assert key in self.fields, f'Unknown field name: {key}'
|
|
||||||
return self.fields[key]
|
|
||||||
else:
|
|
||||||
return list(self.fields.values())[key]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'index {self.index}\n' +\
|
|
||||||
f'name {self.name}\n' +\
|
|
||||||
f'stb {self.stb} ({self.stb_description})\n' +\
|
|
||||||
f'info {self.info} ({self.description})\n' +\
|
|
||||||
'\n'.join(f"{k:18} {v:4} {fdat.symbol_fields[k]}" for k, v in self.fields.items()) + '\n'
|
|
||||||
|
|
||||||
|
|
||||||
class elf_section():
|
|
||||||
"""A class for representing data of an ELF section
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
file: Points to the parent ELF file object.
|
|
||||||
name: Name of the section
|
|
||||||
index: Absolut index of the section
|
|
||||||
type: Type of the section
|
|
||||||
description: Description of the section type
|
|
||||||
fields: All symbol header fields as dict
|
|
||||||
"""
|
|
||||||
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.file = file
|
|
||||||
self.index = index
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
if fields['sh_type'] > 0x60000000:
|
|
||||||
# Range for OS, compiler and application specific types
|
|
||||||
self.description = [v for k, v in fdat.section_header_types_ex.items() if k >= fields['sh_type']][0]
|
|
||||||
self.type = str(hex(fields['sh_type']))
|
|
||||||
elif fields['sh_type'] in fdat.section_header_types:
|
|
||||||
self.type, self.description = fdat.section_header_types[fields['sh_type']]
|
|
||||||
else:
|
|
||||||
self.description = ''
|
|
||||||
self.type = str(hex(fields['sh_type']))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data(self) -> bytes:
|
|
||||||
"""Returns the binary data from the section.
|
|
||||||
The offset in the ELF file is given by: section.sh_offset
|
|
||||||
"""
|
|
||||||
if self.type == 'SHT_NOBITS':
|
|
||||||
return b'\x00' * self['sh_size']
|
|
||||||
else:
|
|
||||||
return self.file.read_bytes(self['sh_offset'], self['sh_size'])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def symbols(self) -> 'symbol_list':
|
|
||||||
"""All ELF symbols associated with this section
|
|
||||||
"""
|
|
||||||
return symbol_list(self.file.list_symbols(self.index))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data_hex(self) -> str:
|
|
||||||
"""Returns the binary data from the section as hex string.
|
|
||||||
"""
|
|
||||||
return ' '.join(f'{d:02X}' for d in self.data)
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[str, int]) -> int:
|
|
||||||
if isinstance(key, str):
|
|
||||||
assert key in self.fields, f'Unknown field name: {key}'
|
|
||||||
return self.fields[key]
|
|
||||||
else:
|
|
||||||
return list(self.fields.values())[key]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'index {self.index}\n' +\
|
|
||||||
f'name {self.name}\n' +\
|
|
||||||
f'type {self.type} ({self.description})\n' +\
|
|
||||||
'\n'.join(f"{k:18} {v:4} {fdat.section_header[k]['description']}" for k, v in self.fields.items()) + '\n'
|
|
||||||
|
|
||||||
|
|
||||||
class elf_relocation():
|
|
||||||
"""A class for representing data of a relocation
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
file: Points to the parent ELF file object.
|
|
||||||
index: Absolut index of the relocation in the associated relocation section
|
|
||||||
symbol: Symbol to relocate
|
|
||||||
type: Type of the relocation
|
|
||||||
calculation: Description of the relocation calculation
|
|
||||||
bits: number ob bits to patch by the relocation
|
|
||||||
target_section: Pointing to the section where this relocation applies to
|
|
||||||
fields: All relocation header fields as dict
|
|
||||||
"""
|
|
||||||
def __init__(self, file: 'elf_file', fields: dict[str, int], symbol_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.file = file
|
|
||||||
self.index = index
|
|
||||||
self.symbol = file.symbols[symbol_index]
|
|
||||||
reloc_types = fdat.relocation_table_types.get(file.architecture)
|
|
||||||
if reloc_types and relocation_type in reloc_types:
|
|
||||||
self.type = reloc_types[relocation_type][0]
|
|
||||||
self.bits = reloc_types[relocation_type][1]
|
|
||||||
self.calculation = reloc_types[relocation_type][2]
|
|
||||||
else:
|
|
||||||
self.type = str(relocation_type)
|
|
||||||
self.bits = 0
|
|
||||||
self.calculation = ''
|
|
||||||
self.target_section: elf_section = file.sections[sh_info]
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[str, int]) -> int:
|
|
||||||
if isinstance(key, str):
|
|
||||||
assert key in self.fields, f'Unknown field name: {key}'
|
|
||||||
return self.fields[key]
|
|
||||||
else:
|
|
||||||
return list(self.fields.values())[key]
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f'index {self.symbol.index}\n' +\
|
|
||||||
f'symbol {self.symbol.name}\n' +\
|
|
||||||
f'relocation type {self.type}\n' +\
|
|
||||||
f'calculation {self.calculation}\n' +\
|
|
||||||
f'bits {self.bits}\n' +\
|
|
||||||
'\n'.join(f'{k:18} {v:4}' for k, v in self.fields.items()) + '\n'
|
|
||||||
|
|
||||||
|
|
||||||
class elf_list(Generic[_T]):
|
|
||||||
"""A generic class for representing a list of ELF data items
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Iterable of ELF data items
|
|
||||||
"""
|
|
||||||
def __init__(self, data: Iterable[_T]):
|
|
||||||
self._data = list(data)
|
|
||||||
|
|
||||||
def __getitem__(self, key: Union[str, int]) -> _T:
|
|
||||||
if isinstance(key, str):
|
|
||||||
elements = [el for el in self._data if getattr(el, 'name', '') == key]
|
|
||||||
assert elements, f'Unknown name: {key}'
|
|
||||||
return elements[0]
|
|
||||||
else:
|
|
||||||
return self._data.__getitem__(key)
|
|
||||||
|
|
||||||
def __len__(self) -> int:
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[_T]:
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
|
||||||
return [], [[]], []
|
|
||||||
|
|
||||||
def _repr_table(self, format: _output_formatter.table_format, raw_data: bool = False) -> str:
|
|
||||||
if raw_data and len(self):
|
|
||||||
table_dict: list[dict[str, int]] = [el.__dict__.get('fields', {' ': 0}) for el in self]
|
|
||||||
columns = list(table_dict[0].keys())
|
|
||||||
data: list[list[Union[str, int]]] = [list(el.values()) for el in table_dict]
|
|
||||||
radj = columns
|
|
||||||
else:
|
|
||||||
columns, data, radj = self._compact_table()
|
|
||||||
return _output_formatter.generate_table(data, columns, right_adj_col=radj, format=format)
|
|
||||||
|
|
||||||
def to_dict_list(self) -> list[dict[str, Union[str, int]]]:
|
|
||||||
"""Exporting the ELF item data table to a list of dicts. It can be used with pandas:
|
|
||||||
df = pandas.DataFrame(elements.to_dict_list())
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Table data
|
|
||||||
"""
|
|
||||||
columns, data, _ = self._compact_table()
|
|
||||||
return [{k: v for k, v in zip(columns, row)} for row in data]
|
|
||||||
|
|
||||||
def to_html(self) -> str:
|
|
||||||
"""Exporting the ELF item data table to HTML.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
HTML table
|
|
||||||
"""
|
|
||||||
return self._repr_table('html')
|
|
||||||
|
|
||||||
def to_markdown(self) -> str:
|
|
||||||
"""Exporting the ELF item data table to markdown.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Markdown table
|
|
||||||
"""
|
|
||||||
return self._repr_table('markdown')
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return self._repr_table('text')
|
|
||||||
|
|
||||||
def _repr_html_(self) -> str:
|
|
||||||
return self._repr_table('html')
|
|
||||||
|
|
||||||
def _repr_markdown_(self) -> str:
|
|
||||||
return self._repr_table('markdown')
|
|
||||||
|
|
||||||
|
|
||||||
class section_list(elf_list[elf_section]):
|
|
||||||
"""A class for representing a list of ELF section
|
|
||||||
"""
|
|
||||||
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
|
||||||
columns = ['index', 'name', 'type', 'description']
|
|
||||||
data: list[list[Union[str, int]]] = [[item.index, item.name, item.type,
|
|
||||||
item.description] for item in self]
|
|
||||||
return columns, data, ['index']
|
|
||||||
|
|
||||||
|
|
||||||
class symbol_list(elf_list[elf_symbol]):
|
|
||||||
"""A class for representing a list of ELF symbols
|
|
||||||
"""
|
|
||||||
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
|
||||||
columns = ['index', 'name', 'info', 'size', 'stb', 'section', 'description']
|
|
||||||
data: list[list[Union[str, int]]] = [[item.index, item.name, item.info, item.fields['st_size'],
|
|
||||||
item.stb, item.section.name if item.section else '', item.description] for item in self]
|
|
||||||
return columns, data, ['index', 'size']
|
|
||||||
|
|
||||||
|
|
||||||
class relocation_list(elf_list[elf_relocation]):
|
|
||||||
"""A class for representing a list of ELF relocations
|
|
||||||
"""
|
|
||||||
def _compact_table(self) -> tuple[list[str], list[list[Union[str, int]]], list[str]]:
|
|
||||||
columns = ['index', 'symbol name', 'type', 'calculation', 'bits']
|
|
||||||
data: list[list[Union[str, int]]] = [[item.index, item.symbol.name, item.type,
|
|
||||||
item.calculation, item.bits] for item in self]
|
|
||||||
return columns, data, ['index', 'bits']
|
|
||||||
|
|
||||||
|
|
||||||
class elf_file:
|
|
||||||
"""A class for representing data of an ELF file in a structured form
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
byteorder: Byte order of the architecture 'little' or 'big'
|
|
||||||
(based on e_ident[EI_DATA])
|
|
||||||
bit_width: Bit with of the architecture: 32 or 64 (based on
|
|
||||||
e_ident[EI_CLASS])
|
|
||||||
architecture: Name of the architecture (based on e_machine)
|
|
||||||
fields: All ELF header fields as dict
|
|
||||||
sections: A list of all ELF sections
|
|
||||||
symbols: A list of all ELF symbols
|
|
||||||
functions: A list of all function symbols (STT_FUNC)
|
|
||||||
objects: A list of all variable/object symbols (STT_OBJECT)
|
|
||||||
code_relocations: A list of all code relocations (.rela.text and .rel.text)
|
|
||||||
symbol_table_section: The symbol table section (first section with
|
|
||||||
the type SHT_SYMTAB)
|
|
||||||
string_table_section: The string table section (first section with
|
|
||||||
the name .strtab)
|
|
||||||
"""
|
|
||||||
def __init__(self, data: Union[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.'
|
|
||||||
self._data = bytes(data)
|
|
||||||
|
|
||||||
# Defaults required for function _read_int_from_elf_field
|
|
||||||
self.bit_width = 32
|
|
||||||
self.byteorder: Literal['little', 'big'] = 'little'
|
|
||||||
|
|
||||||
assert self._read_bytes_from_elf_field('e_ident[EI_MAG]') == b'\x7fELF', 'Not an ELF file'
|
|
||||||
|
|
||||||
self.bit_width = {1: 32, 2: 64}[self._read_int_from_elf_field('e_ident[EI_CLASS]')]
|
|
||||||
|
|
||||||
byte_order = self._read_int_from_elf_field('e_ident[EI_DATA]')
|
|
||||||
assert byte_order in [1, 2], 'Invalid byte order value e_ident[EI_DATA]'
|
|
||||||
self.byteorder = 'little' if byte_order == 1 else 'big'
|
|
||||||
|
|
||||||
self.fields = {fn: self._read_int_from_elf_field(fn) for fn in fdat.elf_header_field.keys()}
|
|
||||||
|
|
||||||
arch_entr = fdat.e_machine_dict.get(self.fields['e_machine'])
|
|
||||||
self.architecture = arch_entr[0] if arch_entr else str(self.fields['e_machine'])
|
|
||||||
|
|
||||||
section_data = list(self._list_sections())
|
|
||||||
name_addr: dict[str, int] = section_data[self.fields['e_shstrndx']] if section_data else dict()
|
|
||||||
section_names = (self.read_string(name_addr['sh_offset'] + f['sh_name']) for f in section_data)
|
|
||||||
|
|
||||||
self.sections = section_list(elf_section(self, sd, sn, i)
|
|
||||||
for i, (sd, sn) in enumerate(zip(section_data, section_names)))
|
|
||||||
|
|
||||||
ret_sections = [sh for sh in self.sections if sh.type == 'SHT_SYMTAB']
|
|
||||||
self.symbol_table_section = ret_sections[0] if ret_sections else None
|
|
||||||
|
|
||||||
ret_sections = [sh for sh in self.sections if sh.name == '.strtab']
|
|
||||||
self.string_table_section = ret_sections[0] if ret_sections else None
|
|
||||||
|
|
||||||
self.symbols = symbol_list(self.list_symbols())
|
|
||||||
|
|
||||||
self.functions = symbol_list(s for s in self.symbols if s.info == 'STT_FUNC')
|
|
||||||
self.objects = symbol_list(s for s in self.symbols if s.info == 'STT_OBJECT')
|
|
||||||
|
|
||||||
self.code_relocations = self.get_relocations(['.rela.text', '.rel.text'])
|
|
||||||
|
|
||||||
def _list_sections(self) -> Generator[dict[str, int], None, None]:
|
|
||||||
for i in range(self.fields['e_shnum']):
|
|
||||||
offs = self.fields['e_shoff'] + i * self.fields['e_shentsize']
|
|
||||||
yield {fn: self._read_from_sh_field(offs, fn) for fn in fdat.section_header.keys()}
|
|
||||||
|
|
||||||
def list_symbols(self, section_index: Optional[int] = None) -> Generator[elf_symbol, None, None]:
|
|
||||||
"""List ELF symbols.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
section_index: If provided, only symbols from the specified section are returned.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of ELF symbols
|
|
||||||
"""
|
|
||||||
if self.symbol_table_section:
|
|
||||||
offs = self.symbol_table_section['sh_offset']
|
|
||||||
|
|
||||||
for j, i in enumerate(range(offs, self.symbol_table_section['sh_size'] + offs, self.symbol_table_section['sh_entsize'])):
|
|
||||||
ret = {'st_name': self.read_int(i, 4)}
|
|
||||||
|
|
||||||
if self.bit_width == 32:
|
|
||||||
ret['st_value'] = self.read_int(i + 4, 4)
|
|
||||||
ret['st_size'] = self.read_int(i + 8, 4)
|
|
||||||
ret['st_info'] = self.read_int(i + 12, 1)
|
|
||||||
ret['st_other'] = self.read_int(i + 13, 1)
|
|
||||||
ret['st_shndx'] = self.read_int(i + 14, 2)
|
|
||||||
elif self.bit_width == 64:
|
|
||||||
ret['st_info'] = self.read_int(i + 4, 1)
|
|
||||||
ret['st_other'] = self.read_int(i + 5, 1)
|
|
||||||
ret['st_shndx'] = self.read_int(i + 6, 2)
|
|
||||||
ret['st_value'] = self.read_int(i + 8, 8)
|
|
||||||
ret['st_size'] = self.read_int(i + 16, 8)
|
|
||||||
|
|
||||||
if section_index is None or section_index == ret['st_shndx']:
|
|
||||||
yield elf_symbol(self, ret, j)
|
|
||||||
|
|
||||||
def get_relocations(self, reloc_section: Optional[Union[elf_section, str, list[str]]] = None) -> relocation_list:
|
|
||||||
"""List relocations.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
reloc_section: Specifies the relocation section from which the
|
|
||||||
relocations should be listed. It can be provided as
|
|
||||||
elf_section object or by its name. If not provided
|
|
||||||
(reloc_section=None) relocations from all relocation
|
|
||||||
sections are returned.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of relocations
|
|
||||||
"""
|
|
||||||
if isinstance(reloc_section, elf_section):
|
|
||||||
assert reloc_section.type in ('SHT_REL', 'SHT_RELA'), f'{reloc_section.name} is not a relocation section'
|
|
||||||
return relocation_list(self._list_relocations(reloc_section))
|
|
||||||
else:
|
|
||||||
relocations: list[elf_relocation] = list()
|
|
||||||
for sh in self.sections:
|
|
||||||
if sh.type in ('SHT_REL', 'SHT_RELA'):
|
|
||||||
if reloc_section is None or \
|
|
||||||
(isinstance(reloc_section, str) and sh.name == reloc_section) or \
|
|
||||||
(isinstance(reloc_section, list) and sh.name in reloc_section):
|
|
||||||
relocations += relocation_list(self._list_relocations(sh))
|
|
||||||
|
|
||||||
return relocation_list(relocations)
|
|
||||||
|
|
||||||
def _list_relocations(self, sh: elf_section) -> Generator[elf_relocation, None, None]:
|
|
||||||
"""List relocations for a elf_section.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
elf_section: Specifies the relocation section from which the
|
|
||||||
relocations should be listed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Relocations from specified elf_section
|
|
||||||
"""
|
|
||||||
offs = sh['sh_offset']
|
|
||||||
for i, el_off in enumerate(range(offs, sh['sh_size'] + offs, sh['sh_entsize'])):
|
|
||||||
ret: dict[str, int] = dict()
|
|
||||||
|
|
||||||
if self.bit_width == 32:
|
|
||||||
ret['r_offset'] = self.read_int(el_off, 4)
|
|
||||||
r_info = self.read_int(el_off + 4, 4)
|
|
||||||
ret['r_info'] = r_info
|
|
||||||
ret['r_addend'] = self.read_int(el_off + 8, 4, True) if sh.type == 'SHT_RELA' else 0
|
|
||||||
yield elf_relocation(self, ret, r_info >> 8, r_info & 0xFF, sh['sh_info'], i)
|
|
||||||
elif self.bit_width == 64:
|
|
||||||
ret['r_offset'] = self.read_int(el_off, 8)
|
|
||||||
r_info = self.read_int(el_off + 8, 8)
|
|
||||||
ret['r_info'] = r_info
|
|
||||||
ret['r_addend'] = self.read_int(el_off + 16, 8, True) if sh.type == 'SHT_RELA' else 0
|
|
||||||
yield elf_relocation(self, ret, r_info >> 32, r_info & 0xFFFFFFFF, sh['sh_info'], i)
|
|
||||||
|
|
||||||
def read_bytes(self, offset: int, num_bytes: int) -> bytes:
|
|
||||||
"""Read bytes from ELF file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
offset: Specify first byte relative to the start of
|
|
||||||
the ELF file.
|
|
||||||
num_bytes: Specify the number of bytes to read.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Binary data as bytes
|
|
||||||
"""
|
|
||||||
return self._data[offset:offset + num_bytes]
|
|
||||||
|
|
||||||
def read_int(self, offset: int, num_bytes: int, signed: bool = False) -> int:
|
|
||||||
"""Read an integer from the ELF file. Byte order is
|
|
||||||
selected according to the architecture (e_ident[EI_DATA]).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
offset: Specify first byte of the integer relative to
|
|
||||||
the start of the ELF file.
|
|
||||||
num_bytes: Specify the size of the integer in bytes.
|
|
||||||
signed: Select if the integer is a signed integer.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Integer value
|
|
||||||
"""
|
|
||||||
return int.from_bytes(self._data[offset:offset + num_bytes], self.byteorder, signed=signed)
|
|
||||||
|
|
||||||
# def int_to_bytes(self, value: int, num_bytes: int = 4, signed: bool = False) -> int:
|
|
||||||
# return value.to_bytes(length=num_bytes, byteorder=self.byteorder, signed=signed)
|
|
||||||
|
|
||||||
def read_string(self, offset: int, encoding: str = 'utf-8') -> str:
|
|
||||||
"""Read a zero-terminated text string from the ELF file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
offset: Specify first byte of the string relative to
|
|
||||||
the start of the ELF file.
|
|
||||||
encoding: Encoding used for text decoding.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Text string
|
|
||||||
"""
|
|
||||||
str_end = self._data.find(b'\x00', offset)
|
|
||||||
return self._data[offset:str_end].decode(encoding)
|
|
||||||
|
|
||||||
def _read_int_from_elf_field(self, field_name: str) -> int:
|
|
||||||
field = fdat.elf_header_field[field_name]
|
|
||||||
offs = int(field[str(self.bit_width)], base=16)
|
|
||||||
byte_len = int(field['size' + str(self.bit_width)])
|
|
||||||
return self.read_int(offs, byte_len)
|
|
||||||
|
|
||||||
def _read_bytes_from_elf_field(self, field_name: str) -> bytes:
|
|
||||||
field = fdat.elf_header_field[field_name]
|
|
||||||
offs = int(field[str(self.bit_width)], base=16)
|
|
||||||
byte_len = int(field['size' + str(self.bit_width)])
|
|
||||||
return self.read_bytes(offs, byte_len)
|
|
||||||
|
|
||||||
def _read_from_sh_field(self, offset: int, field_name: str) -> int:
|
|
||||||
field = fdat.section_header[field_name]
|
|
||||||
offs = int(field[str(self.bit_width)], base=16) + offset
|
|
||||||
byte_len = int(field['size' + str(self.bit_width)])
|
|
||||||
return self.read_int(offs, byte_len)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
hf_list = ((hf, self.fields[hf['field_name']]) for hf in fdat.elf_header_field.values())
|
|
||||||
return '\n'.join(f"{hf['field_name']:24} {v:4} {hf['description']}" for hf, v in hf_list) + '\n'
|
|
||||||
|
|
||||||
def __getitem__(self, key: str) -> int:
|
|
||||||
assert key in self.fields, f'Unknown field name: {key}'
|
|
||||||
return self.fields[key]
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
import pelfy._main as _main
|
import pelfy
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ def test_simple_c() -> None:
|
||||||
assert file_list, "No test object files found"
|
assert file_list, "No test object files found"
|
||||||
for path in file_list:
|
for path in file_list:
|
||||||
print(f'Open {path}...')
|
print(f'Open {path}...')
|
||||||
elf = _main.open_elf_file(path)
|
elf = pelfy.open_elf_file(path)
|
||||||
|
|
||||||
print(elf)
|
print(elf)
|
||||||
print(elf.sections)
|
print(elf.sections)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import pelfy._main as _main
|
import pelfy
|
||||||
|
|
||||||
|
|
||||||
def test_readme_example() -> None:
|
def test_readme_example() -> None:
|
||||||
elf = _main.open_elf_file('tests/obj/test-c-riscv64-linux-gnu-gcc-12-O3.o')
|
elf = pelfy.open_elf_file('tests/obj/test-c-riscv64-linux-gnu-gcc-12-O3.o')
|
||||||
|
|
||||||
assert ' description ' in elf.sections.to_markdown()
|
assert ' description ' in elf.sections.to_markdown()
|
||||||
assert ' stb ' in elf.functions.to_markdown()
|
assert ' stb ' in elf.functions.to_markdown()
|
||||||
|
|
Loading…
Reference in New Issue