emtrace 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emtrace-0.2.0/.gitignore +2 -0
- {emtrace-0.1.2 → emtrace-0.2.0}/LICENSE +1 -1
- {emtrace-0.1.2 → emtrace-0.2.0}/PKG-INFO +3 -3
- emtrace-0.2.0/emtrace/__init__.py +64 -0
- emtrace-0.2.0/emtrace/__main__.py +5 -0
- emtrace-0.2.0/emtrace/_build_info.py +4 -0
- emtrace-0.2.0/emtrace/_lief.py +74 -0
- emtrace-0.2.0/emtrace/cobs.py +64 -0
- emtrace-0.2.0/emtrace/edit.py +103 -0
- emtrace-0.2.0/emtrace/elf_backend.py +151 -0
- emtrace-0.2.0/emtrace/engine.py +490 -0
- emtrace-0.2.0/emtrace/errors.py +78 -0
- emtrace-0.2.0/emtrace/exemod.py +183 -0
- emtrace-0.2.0/emtrace/format_info.py +112 -0
- emtrace-0.2.0/emtrace/format_types.py +59 -0
- emtrace-0.2.0/emtrace/formatters.py +22 -0
- emtrace-0.2.0/emtrace/inspector.py +981 -0
- emtrace-0.2.0/emtrace/macho_backend.py +147 -0
- emtrace-0.2.0/emtrace/parser.py +322 -0
- emtrace-0.2.0/emtrace/pe_backend.py +117 -0
- emtrace-0.2.0/emtrace/record_parser.py +619 -0
- emtrace-0.2.0/hatch_build.py +73 -0
- {emtrace-0.1.2 → emtrace-0.2.0}/pyproject.toml +14 -10
- emtrace-0.1.2/.gitignore +0 -1
- emtrace-0.1.2/emtrace/__init__.py +0 -2
- {emtrace-0.1.2 → emtrace-0.2.0}/README.md +0 -0
emtrace-0.2.0/.gitignore
ADDED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright
|
|
189
|
+
Copyright 2026 Jonas Beyer
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emtrace
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A tool to parse and format emtrace data.
|
|
5
5
|
Author-email: Jonas Beyer <reyeb.sanoj@googlemail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -191,7 +191,7 @@ License: Apache License
|
|
|
191
191
|
same "printed page" as the copyright notice for easier
|
|
192
192
|
identification within third-party archives.
|
|
193
193
|
|
|
194
|
-
Copyright
|
|
194
|
+
Copyright 2026 Jonas Beyer
|
|
195
195
|
|
|
196
196
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
197
197
|
you may not use this file except in compliance with the License.
|
|
@@ -206,7 +206,7 @@ License: Apache License
|
|
|
206
206
|
limitations under the License.
|
|
207
207
|
License-File: LICENSE
|
|
208
208
|
Keywords: embedded,logging,tracing
|
|
209
|
-
Requires-Dist:
|
|
209
|
+
Requires-Dist: lief>=0.17
|
|
210
210
|
Provides-Extra: test
|
|
211
211
|
Requires-Dist: pytest-xdist>=3.6.1; extra == 'test'
|
|
212
212
|
Requires-Dist: pytest>=8.2.2; extra == 'test'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""emtrace: parse and format emtrace trace data (pure-python library).
|
|
2
|
+
|
|
3
|
+
The command-line interface lives in :mod:`emtrace.cli` and is intentionally
|
|
4
|
+
*not* imported here, so ``import emtrace`` stays lightweight and free of
|
|
5
|
+
CLI side effects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.2.0"
|
|
9
|
+
|
|
10
|
+
from .edit import nonload_binary, strip_binary
|
|
11
|
+
from .engine import (
|
|
12
|
+
FormatInfoResult,
|
|
13
|
+
RunReport,
|
|
14
|
+
TraceInfoFormat,
|
|
15
|
+
emtrace,
|
|
16
|
+
load_format_info,
|
|
17
|
+
)
|
|
18
|
+
from .errors import Issue, Report
|
|
19
|
+
from .exemod import (
|
|
20
|
+
ExeError,
|
|
21
|
+
ExeImage,
|
|
22
|
+
NonloadReport,
|
|
23
|
+
NotSupportedError,
|
|
24
|
+
SectionNotFoundError,
|
|
25
|
+
StripReport,
|
|
26
|
+
)
|
|
27
|
+
from .inspector import (
|
|
28
|
+
ArchiveDetails,
|
|
29
|
+
BinaryDetails,
|
|
30
|
+
CheckReport,
|
|
31
|
+
ExportReport,
|
|
32
|
+
InspectReport,
|
|
33
|
+
RawDetails,
|
|
34
|
+
check_section,
|
|
35
|
+
export_section,
|
|
36
|
+
inspect_path,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"ArchiveDetails",
|
|
41
|
+
"BinaryDetails",
|
|
42
|
+
"CheckReport",
|
|
43
|
+
"ExeError",
|
|
44
|
+
"ExeImage",
|
|
45
|
+
"ExportReport",
|
|
46
|
+
"FormatInfoResult",
|
|
47
|
+
"InspectReport",
|
|
48
|
+
"Issue",
|
|
49
|
+
"NonloadReport",
|
|
50
|
+
"NotSupportedError",
|
|
51
|
+
"RawDetails",
|
|
52
|
+
"Report",
|
|
53
|
+
"RunReport",
|
|
54
|
+
"SectionNotFoundError",
|
|
55
|
+
"StripReport",
|
|
56
|
+
"TraceInfoFormat",
|
|
57
|
+
"check_section",
|
|
58
|
+
"emtrace",
|
|
59
|
+
"export_section",
|
|
60
|
+
"inspect_path",
|
|
61
|
+
"load_format_info",
|
|
62
|
+
"nonload_binary",
|
|
63
|
+
"strip_binary",
|
|
64
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Sole adapter for the optional ``lief`` dependency.
|
|
2
|
+
|
|
3
|
+
``lief`` is a default install dependency of the ``emtrace`` package, but the
|
|
4
|
+
library also works without it: anything that does not need to parse an
|
|
5
|
+
executable (raw ``binary`` and ``json`` inputs, record-level helpers) keeps
|
|
6
|
+
working, while executable-based operations report a clear "lief is not
|
|
7
|
+
installed" issue instead of failing at import time.
|
|
8
|
+
|
|
9
|
+
This module is the only place that touches ``lief`` availability:
|
|
10
|
+
|
|
11
|
+
- ``HAS_LIEF`` tells callers whether the dependency is present.
|
|
12
|
+
- ``parse_binary()`` / ``find_section_bytes()`` are defined exactly once
|
|
13
|
+
(typing-only imports under ``TYPE_CHECKING`` keep type checkers happy
|
|
14
|
+
without requiring the package at runtime).
|
|
15
|
+
- :func:`lief_missing_error` builds the standard issue message so every
|
|
16
|
+
command refuses identically.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import lief as _lief
|
|
27
|
+
except ImportError:
|
|
28
|
+
_lief = None # type: ignore[assignment]
|
|
29
|
+
|
|
30
|
+
#: Whether the optional ``lief`` dependency is installed.
|
|
31
|
+
HAS_LIEF: bool = _lief is not None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def lief_missing_error(operation: str) -> str:
|
|
35
|
+
"""The standard message for refusing *operation* without lief installed."""
|
|
36
|
+
return (
|
|
37
|
+
f"{operation} requires the 'lief' package, which is not installed "
|
|
38
|
+
f"(install the default 'emtrace' dependencies to enable it)"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def parse_binary(kind: str, path: Path) -> Any | None:
|
|
43
|
+
"""Parse *path* as a binary of *kind* (Tier 1: ``None`` when unavailable).
|
|
44
|
+
|
|
45
|
+
*kind* is one of ``"exe"``, ``"elf"``, ``"pe"``, ``"macho"`` or ``"oat"``.
|
|
46
|
+
Returns ``None`` when lief is not installed, when the file is not of the
|
|
47
|
+
requested kind, or when parsing fails.
|
|
48
|
+
"""
|
|
49
|
+
if _lief is None:
|
|
50
|
+
return None
|
|
51
|
+
if kind == "exe":
|
|
52
|
+
return _lief.parse(path)
|
|
53
|
+
if kind == "elf":
|
|
54
|
+
return _lief.ELF.parse(path)
|
|
55
|
+
if kind == "pe":
|
|
56
|
+
return _lief.PE.parse(path)
|
|
57
|
+
if kind == "macho":
|
|
58
|
+
return _lief.parse(path) if _lief.is_macho(path) else None
|
|
59
|
+
if kind == "oat":
|
|
60
|
+
return _lief.OAT.parse(path)
|
|
61
|
+
raise ValueError(f"unknown binary kind: {kind!r}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def find_section_bytes(
|
|
65
|
+
trace_info_file: Path, section_name: str, parse: Callable[[Path], Any | None]
|
|
66
|
+
) -> bytes | None:
|
|
67
|
+
"""Extract section bytes via *parse* (Tier 1: ``None`` when absent)."""
|
|
68
|
+
exe = parse(trace_info_file)
|
|
69
|
+
if exe is None:
|
|
70
|
+
return None
|
|
71
|
+
section = exe.get_section(section_name)
|
|
72
|
+
if section is None:
|
|
73
|
+
return None
|
|
74
|
+
return bytes(section.content)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def cobs_get_frame(
|
|
7
|
+
istream: Callable[[int], bytes], passthrough: Callable[[bytes], Any] | None = None
|
|
8
|
+
) -> bytes | None:
|
|
9
|
+
"""
|
|
10
|
+
Decodes and returns the next COBS frame in the given input stream.
|
|
11
|
+
The stream is queried until a full COBS frame was received or the stream is empty.
|
|
12
|
+
Optionally provide a passthrough function to decode according to COBS "passthrough" rules.
|
|
13
|
+
In that case frames have to start with a null-byte as well as ending in one,
|
|
14
|
+
and any bytes before the first null-byte, are "passed through" to the provided callback.
|
|
15
|
+
Since a COBS frame cannot consist of just a single null-byte,
|
|
16
|
+
a pair of null-bytes (which would otherwise mean starting and immediately ending a frame)
|
|
17
|
+
is interpreted as an escaped null-byte in the passthrough stream.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
buffer = bytearray()
|
|
21
|
+
data = istream(1)
|
|
22
|
+
|
|
23
|
+
if passthrough is not None:
|
|
24
|
+
while len(data) != 0:
|
|
25
|
+
if data[0] == 0:
|
|
26
|
+
data = istream(1)
|
|
27
|
+
if len(data) == 0:
|
|
28
|
+
return None
|
|
29
|
+
# two successive null-bytes in input are interpreted as an escaped null-byte
|
|
30
|
+
# in the passthrough stream
|
|
31
|
+
if data[0] != 0:
|
|
32
|
+
break
|
|
33
|
+
passthrough(data)
|
|
34
|
+
data = istream(1)
|
|
35
|
+
|
|
36
|
+
if len(data) == 0:
|
|
37
|
+
return None
|
|
38
|
+
if data[0] == 0:
|
|
39
|
+
print("COBS decoding error: zero byte at start of frame", file=sys.stderr)
|
|
40
|
+
return bytes(buffer)
|
|
41
|
+
|
|
42
|
+
while True:
|
|
43
|
+
block_prefix = data[0]
|
|
44
|
+
|
|
45
|
+
block_size = 254 if block_prefix == 0xFF else block_prefix - 1
|
|
46
|
+
|
|
47
|
+
data = istream(block_size)
|
|
48
|
+
if 0 in data:
|
|
49
|
+
print(
|
|
50
|
+
f"COBS decoding error: zero byte in data block: {data}", file=sys.stderr
|
|
51
|
+
)
|
|
52
|
+
buffer.extend(data)
|
|
53
|
+
if len(data) < block_size:
|
|
54
|
+
if len(buffer) == 0:
|
|
55
|
+
return None
|
|
56
|
+
return bytes(buffer)
|
|
57
|
+
assert len(data) == block_size
|
|
58
|
+
|
|
59
|
+
data = istream(1)
|
|
60
|
+
if len(data) == 0 or data[0] == 0:
|
|
61
|
+
return bytes(buffer)
|
|
62
|
+
if block_prefix != 0xFF:
|
|
63
|
+
buffer.append(0)
|
|
64
|
+
block_prefix = -1
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Post-link editing operations for emtrace sections (library core).
|
|
2
|
+
|
|
3
|
+
``strip`` removes the section completely (ELF, PE, and Mach-O); ``nonload``
|
|
4
|
+
makes the section non-loadable on ELF, the same effect as an ``(INFO)``
|
|
5
|
+
region in a linker script (clear SHF_ALLOC and sh_addr, keep the section
|
|
6
|
+
bytes in the file so the traces remain parseable).
|
|
7
|
+
|
|
8
|
+
Both operations rebuild the binary with LIEF; the guarantees are behavioral
|
|
9
|
+
(the section entry is gone, its symbols are deleted, its content no longer
|
|
10
|
+
parses, and the binary still runs) rather than byte-exact.
|
|
11
|
+
|
|
12
|
+
All entry points return :class:`StripReport` / :class:`NonloadReport` with
|
|
13
|
+
``errors`` set on failure — this module never prints. File writing lives in
|
|
14
|
+
:mod:`emtrace.cli.edit_cmds`.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from .exemod import (
|
|
22
|
+
ExeError,
|
|
23
|
+
ExeImage,
|
|
24
|
+
NonloadReport,
|
|
25
|
+
NotSupportedError,
|
|
26
|
+
SectionNotFoundError,
|
|
27
|
+
StripReport,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__all__ = ["nonload_binary", "strip_binary"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _open_image(
|
|
34
|
+
source: Path, command: str, experimental_be: bool
|
|
35
|
+
) -> tuple[ExeImage | None, str | None]:
|
|
36
|
+
"""Open *source* for *command*; return ``(image, None)`` or ``(None, error)``."""
|
|
37
|
+
try:
|
|
38
|
+
return ExeImage.open(source, experimental_be=experimental_be), None
|
|
39
|
+
except (ExeError, NotSupportedError, SectionNotFoundError) as e:
|
|
40
|
+
return None, f"{command}: {e}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def strip_binary(
|
|
44
|
+
path: str | Path,
|
|
45
|
+
section_name: str = ".emtrace",
|
|
46
|
+
*,
|
|
47
|
+
experimental_be: bool = False,
|
|
48
|
+
) -> StripReport:
|
|
49
|
+
"""Remove the section *section_name* from the binary at *path* (Tier 2).
|
|
50
|
+
|
|
51
|
+
On success, ``report.data`` holds the rebuilt binary bytes.
|
|
52
|
+
"""
|
|
53
|
+
source = Path(path).resolve()
|
|
54
|
+
report = StripReport(section=section_name)
|
|
55
|
+
if not source.is_file():
|
|
56
|
+
report.fail(f"{source} does not exist.")
|
|
57
|
+
return report
|
|
58
|
+
|
|
59
|
+
image, open_error = _open_image(source, "strip", experimental_be)
|
|
60
|
+
if image is None:
|
|
61
|
+
report.fail(open_error or f"strip: cannot open {source}.")
|
|
62
|
+
return report
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
inner = image.strip_section(section_name)
|
|
66
|
+
except (SectionNotFoundError, NotSupportedError, ExeError) as e:
|
|
67
|
+
report.fail(f"strip: {e}")
|
|
68
|
+
return report
|
|
69
|
+
|
|
70
|
+
inner.data = image.bytes()
|
|
71
|
+
return inner
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def nonload_binary(
|
|
75
|
+
path: str | Path,
|
|
76
|
+
section_name: str = ".emtrace",
|
|
77
|
+
*,
|
|
78
|
+
experimental_be: bool = False,
|
|
79
|
+
) -> NonloadReport:
|
|
80
|
+
"""Make *section_name* non-loadable in the binary at *path* (Tier 2).
|
|
81
|
+
|
|
82
|
+
On success with changes, ``report.data`` holds the rebuilt binary bytes.
|
|
83
|
+
"""
|
|
84
|
+
source = Path(path).resolve()
|
|
85
|
+
report = NonloadReport(section=section_name)
|
|
86
|
+
if not source.is_file():
|
|
87
|
+
report.fail(f"{source} does not exist.")
|
|
88
|
+
return report
|
|
89
|
+
|
|
90
|
+
image, open_error = _open_image(source, "nonload", experimental_be)
|
|
91
|
+
if image is None:
|
|
92
|
+
report.fail(open_error or f"nonload: cannot open {source}.")
|
|
93
|
+
return report
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
inner = image.make_nonload(section_name)
|
|
97
|
+
except (SectionNotFoundError, NotSupportedError, ExeError) as e:
|
|
98
|
+
report.fail(f"nonload: {e}")
|
|
99
|
+
return report
|
|
100
|
+
|
|
101
|
+
if inner.changed:
|
|
102
|
+
inner.data = image.bytes()
|
|
103
|
+
return inner
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""ELF backend for :mod:`emtrace.exemod` (LIEF-based).
|
|
2
|
+
|
|
3
|
+
Refusal rules (ported from the previous hand-rolled surgery):
|
|
4
|
+
- unlinked object files (ET_REL) are refused; only ET_EXEC/ET_DYN are edited.
|
|
5
|
+
- only data sections (PROGBITS/NOBITS) can be removed: symbol tables, string
|
|
6
|
+
tables, relocations, and other metadata sections are refused, since their
|
|
7
|
+
linkages cannot be expressed safely here.
|
|
8
|
+
- removal is refused when a dynamic symbol references the section (dynamic
|
|
9
|
+
hash tables cannot be rebuilt here) or when a relocation targets the
|
|
10
|
+
section's address range or a deleted symbol.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import lief
|
|
19
|
+
|
|
20
|
+
from .errors import ExeError
|
|
21
|
+
|
|
22
|
+
FORMAT = "elf"
|
|
23
|
+
|
|
24
|
+
_ET_REL = 1
|
|
25
|
+
_ET_EXEC = 2
|
|
26
|
+
_ET_DYN = 3
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _linked_only(binary: Any) -> None:
|
|
30
|
+
file_type = binary.header.file_type
|
|
31
|
+
if file_type not in (
|
|
32
|
+
lief.ELF.Header.FILE_TYPE.EXEC,
|
|
33
|
+
lief.ELF.Header.FILE_TYPE.DYN,
|
|
34
|
+
):
|
|
35
|
+
raise ExeError(
|
|
36
|
+
"input is an unlinked ELF object file; "
|
|
37
|
+
"run this command on a linked executable or shared object instead"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def try_parse(path: Path, *, experimental_be: bool = False) -> Any | None:
|
|
42
|
+
"""Parse *path* as ELF, or return None when the magic does not match.
|
|
43
|
+
|
|
44
|
+
Big-endian inputs are refused unless *experimental_be* is set (their
|
|
45
|
+
rebuild is unverified and may produce corrupt output).
|
|
46
|
+
"""
|
|
47
|
+
with path.open("rb") as f:
|
|
48
|
+
ident = f.read(6)
|
|
49
|
+
if ident[:4] != b"\x7fELF":
|
|
50
|
+
return None
|
|
51
|
+
if ident[5] == 2 and not experimental_be:
|
|
52
|
+
raise ExeError(
|
|
53
|
+
f"cannot edit '{path}': "
|
|
54
|
+
f"big-endian ELF binaries are not supported "
|
|
55
|
+
f"(use --experimental-be to try anyway; "
|
|
56
|
+
f"rebuilding them is unverified)"
|
|
57
|
+
)
|
|
58
|
+
binary = lief.ELF.parse(str(path))
|
|
59
|
+
if binary is None:
|
|
60
|
+
raise ExeError(f"unable to parse '{path}' as ELF")
|
|
61
|
+
_linked_only(binary)
|
|
62
|
+
return binary
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def section_names(binary: Any) -> list[str]:
|
|
66
|
+
"""Names of all sections in the image."""
|
|
67
|
+
return [section.name for section in binary.sections]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def find_section(binary: Any, name: str) -> Any | None:
|
|
71
|
+
"""The section called *name*, or None when it does not exist."""
|
|
72
|
+
return binary.get_section(name)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _section_index(binary: Any, section: Any) -> int:
|
|
76
|
+
return int(binary.get_section_idx(section))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check_strippable(binary: Any, section: Any) -> list[Any]:
|
|
80
|
+
"""Validate that *section* can be removed; return bound static symbols.
|
|
81
|
+
|
|
82
|
+
Raises ExeError when removal is unsafe.
|
|
83
|
+
"""
|
|
84
|
+
sec_type = section.type
|
|
85
|
+
if sec_type not in (
|
|
86
|
+
lief.ELF.Section.TYPE.PROGBITS,
|
|
87
|
+
lief.ELF.Section.TYPE.NOBITS,
|
|
88
|
+
):
|
|
89
|
+
raise ExeError(
|
|
90
|
+
f"cannot remove '{section.name}': "
|
|
91
|
+
f"only data sections can be removed (section type {sec_type})"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
index = _section_index(binary, section)
|
|
95
|
+
|
|
96
|
+
bound = [symbol for symbol in binary.symbols if int(symbol.shndx) == index]
|
|
97
|
+
bound_names = {symbol.name for symbol in bound}
|
|
98
|
+
|
|
99
|
+
for symbol in binary.dynamic_symbols:
|
|
100
|
+
if int(symbol.shndx) == index:
|
|
101
|
+
raise ExeError(
|
|
102
|
+
f"cannot remove '{section.name}': dynamic symbol '{symbol.name}' "
|
|
103
|
+
f"references it (dynamic hash tables cannot be rebuilt here)"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
start = int(section.virtual_address)
|
|
107
|
+
end = start + int(section.size)
|
|
108
|
+
for relocation in binary.relocations:
|
|
109
|
+
address = int(relocation.address)
|
|
110
|
+
if start <= address < end:
|
|
111
|
+
raise ExeError(
|
|
112
|
+
f"cannot remove '{section.name}': "
|
|
113
|
+
f"a relocation targets its address range"
|
|
114
|
+
)
|
|
115
|
+
symbol = relocation.symbol if hasattr(relocation, "symbol") else None
|
|
116
|
+
if symbol is not None and symbol.name in bound_names:
|
|
117
|
+
raise ExeError(
|
|
118
|
+
f"cannot remove '{section.name}': "
|
|
119
|
+
f"a relocation references deleted symbol '{symbol.name}'"
|
|
120
|
+
)
|
|
121
|
+
return bound
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def delete_symbols(binary: Any, symbols: list[Any]) -> int:
|
|
125
|
+
"""Delete static *symbols* from the symbol table; return the count."""
|
|
126
|
+
for symbol in symbols:
|
|
127
|
+
binary.remove_symtab_symbol(symbol)
|
|
128
|
+
return len(symbols)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def remove_section(binary: Any, section: Any) -> None:
|
|
132
|
+
"""Remove *section*, clearing its content so no traces remain."""
|
|
133
|
+
binary.remove(section, clear=True)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def apply_nonload(_binary: Any, section: Any) -> tuple[int, int, bool]:
|
|
137
|
+
"""Clear SHF_ALLOC and the section address; return (old_flags, old_addr, changed)."""
|
|
138
|
+
old_flags = int(section.flags)
|
|
139
|
+
old_addr = int(section.virtual_address)
|
|
140
|
+
if not section.has(lief.ELF.Section.FLAGS.ALLOC):
|
|
141
|
+
return old_flags, old_addr, False
|
|
142
|
+
section.flags = lief.ELF.Section.FLAGS(
|
|
143
|
+
old_flags & ~int(lief.ELF.Section.FLAGS.ALLOC)
|
|
144
|
+
)
|
|
145
|
+
section.virtual_address = 0
|
|
146
|
+
return old_flags, old_addr, True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def rebuild(binary: Any) -> bytes:
|
|
150
|
+
"""Rebuild the binary and return the new file content."""
|
|
151
|
+
return bytes(binary.write_to_bytes())
|