objutils 0.10.0__tar.gz → 0.10.2__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.
- {objutils-0.10.0 → objutils-0.10.2}/PKG-INFO +1 -1
- {objutils-0.10.0 → objutils-0.10.2}/objutils/__init__.py +3 -3
- {objutils-0.10.0 → objutils-0.10.2}/objutils/fpc.py +2 -2
- {objutils-0.10.0 → objutils-0.10.2}/objutils/hexfile.py +50 -22
- {objutils-0.10.0 → objutils-0.10.2}/objutils/ihex.py +8 -30
- {objutils-0.10.0 → objutils-0.10.2}/objutils/image.py +47 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_hex_info.py +1 -9
- {objutils-0.10.0 → objutils-0.10.2}/objutils/section.py +142 -24
- {objutils-0.10.0 → objutils-0.10.2}/objutils/srec.py +1 -1
- objutils-0.10.2/objutils/tests/test_hexfile.py +136 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_image.py +27 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_section.py +44 -0
- objutils-0.10.2/objutils/tests/test_section_join.py +80 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/version.py +1 -1
- {objutils-0.10.0 → objutils-0.10.2}/pyproject.toml +2 -2
- objutils-0.10.0/objutils/tests/test_hexfile.py +0 -65
- {objutils-0.10.0 → objutils-0.10.2}/CMakeLists.txt +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/LICENSE +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/build_ext.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/docs/README.rst +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/.coveragerc +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/.vscode/launch.json +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/.vscode/settings.json +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/a2l_test.shf +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/ash.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/binfile.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/checksums.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/cosmac.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/.traverser.py.un~ +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/attrparser.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/c_generator.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/constants.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/encoding.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/lineprog.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/readers.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/sm.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/dwarf/traverser.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/elf/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/elf/arm/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/elf/arm/attributes.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/elf/defs.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/elf/model.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/emon52.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/etek.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/exceptions.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/ctre.hpp +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/difflib.h +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/exceptions.cpp +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/exceptions.hpp +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/hexfile.cpp +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/extensions/wrapper.cpp +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/hexdump.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/ieee695.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/logger.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/mostec.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/objutils.code-workspace +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/pecoff/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/pecoff/defs.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/pecoff/model.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/pecoff/pdb/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/pickleif.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/rca.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/readers.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/registry.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/arduino_build_artifacts.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_cgen.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_coff_extract.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_coff_import.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_coff_info.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_coff_syms.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_dwarf_import.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_elf_arm_attrs.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_elf_extract.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_elf_import.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_elf_info.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/scripts/oj_elf_syms.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/shf.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/sig.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tek.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_arm_attributes.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_ash.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_c_generator.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_checksums.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_cygpath.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_diff_bin.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_elf.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_emon52.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_examples_cgen.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_fpc.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_hexdump.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_ihex.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_mostec.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_readers.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_registry.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_repr.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_shf.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_sm.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_srec.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_tek.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/tests/test_titext.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/titxt.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/utils/__init__.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/utils/arduino.py +0 -0
- {objutils-0.10.0 → objutils-0.10.2}/objutils/utils/diff.py +0 -0
|
@@ -13,7 +13,7 @@ Registers CODECS and implements an interface to them.
|
|
|
13
13
|
The first parameter is always the codec name.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
__version__ = "0.10.
|
|
16
|
+
__version__ = "0.10.2"
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"Image",
|
|
@@ -125,7 +125,7 @@ registry.register("ash", objutils.ash.Reader, objutils.ash.Writer, "ASCII hex sp
|
|
|
125
125
|
registry.register("shf", objutils.shf.Reader, objutils.shf.Writer, "S Hexdump Format (rfc4149).")
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def load(codec_name: str, fp: str | Path | BinaryIO, join: bool =
|
|
128
|
+
def load(codec_name: str, fp: str | Path | BinaryIO, join: bool = True, **kws: Any) -> Image:
|
|
129
129
|
"""Load hex data from file.
|
|
130
130
|
|
|
131
131
|
Parameters
|
|
@@ -140,7 +140,7 @@ def load(codec_name: str, fp: str | Path | BinaryIO, join: bool = False, **kws:
|
|
|
140
140
|
return registry.get(codec_name).Reader().load(fp, join=join, **kws)
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
def loads(codec_name: str, data: str | bytes | bytearray, join: bool =
|
|
143
|
+
def loads(codec_name: str, data: str | bytes | bytearray, join: bool = True, **kws: Any) -> Image:
|
|
144
144
|
"""Load hex data from bytes.
|
|
145
145
|
|
|
146
146
|
Parameters
|
|
@@ -108,7 +108,7 @@ class Reader(hexfile.Reader):
|
|
|
108
108
|
out_lines.append("".join(values))
|
|
109
109
|
return "\n".join(out_lines)
|
|
110
110
|
|
|
111
|
-
def read(self, fp: BinaryIO) -> Image:
|
|
111
|
+
def read(self, fp: BinaryIO, join: bool = False) -> Image:
|
|
112
112
|
"""Read FPC file and convert to Image.
|
|
113
113
|
|
|
114
114
|
Args:
|
|
@@ -117,7 +117,7 @@ class Reader(hexfile.Reader):
|
|
|
117
117
|
Returns:
|
|
118
118
|
Image object containing decoded sections
|
|
119
119
|
"""
|
|
120
|
-
return super().read(create_string_buffer(bytearray(self.decode(fp), "ascii")))
|
|
120
|
+
return super().read(create_string_buffer(bytearray(self.decode(fp), "ascii")), join=join)
|
|
121
121
|
|
|
122
122
|
def convert_quintuple(self, quintuple: str) -> int:
|
|
123
123
|
"""Convert 5-character base-85 quintuple to 32-bit integer.
|
|
@@ -499,16 +499,21 @@ class FormatParser:
|
|
|
499
499
|
# ============================================================================
|
|
500
500
|
|
|
501
501
|
|
|
502
|
+
@dataclass
|
|
502
503
|
class Container:
|
|
503
|
-
"""
|
|
504
|
-
|
|
505
|
-
Deprecated: Use ParsedRecord instead.
|
|
506
|
-
"""
|
|
504
|
+
"""Modern attribute container for parsed hex records."""
|
|
507
505
|
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
line_number: int = 0
|
|
507
|
+
address: Optional[int] = None
|
|
508
|
+
length: Optional[int] = None
|
|
509
|
+
type: Optional[int] = None
|
|
510
|
+
checksum: Optional[int] = None
|
|
511
|
+
chunk: Optional[bytearray] = None
|
|
512
|
+
junk: Optional[str] = None
|
|
513
|
+
processing_instructions: list[Any] = field(default_factory=list)
|
|
510
514
|
|
|
511
515
|
def add_processing_instruction(self, pi: Any) -> None:
|
|
516
|
+
"""Add a processing instruction to the record."""
|
|
512
517
|
self.processing_instructions.append(pi)
|
|
513
518
|
|
|
514
519
|
|
|
@@ -785,6 +790,7 @@ class Reader(BaseType):
|
|
|
785
790
|
self.stats = Statistics()
|
|
786
791
|
self.valid = True
|
|
787
792
|
self.formats: list[tuple[int, re.Pattern[str]]] = []
|
|
793
|
+
self.base_address = 0 # Base address for relative addressing (if applicable - mainly Intel HEX)
|
|
788
794
|
|
|
789
795
|
# Parse FORMAT_SPEC into compiled patterns
|
|
790
796
|
if isinstance(self.FORMAT_SPEC, str):
|
|
@@ -831,6 +837,21 @@ class Reader(BaseType):
|
|
|
831
837
|
buffer = create_string_buffer(image)
|
|
832
838
|
return self.load(buffer, join=join)
|
|
833
839
|
|
|
840
|
+
def _parse_optional_int(self, groups: dict[str, Optional[str]], key: str) -> Optional[int]:
|
|
841
|
+
"""Parse an optional numeric capture group using ``atoi``.
|
|
842
|
+
|
|
843
|
+
Args:
|
|
844
|
+
groups: Regex named groups from a parsed line.
|
|
845
|
+
key: Group name to parse.
|
|
846
|
+
|
|
847
|
+
Returns:
|
|
848
|
+
Parsed integer or ``None`` if the group is missing/empty.
|
|
849
|
+
"""
|
|
850
|
+
value = groups.get(key)
|
|
851
|
+
if value is None or value == "":
|
|
852
|
+
return None
|
|
853
|
+
return atoi(value)
|
|
854
|
+
|
|
834
855
|
def read(self, fp: BinaryIO, join: bool = False) -> Image:
|
|
835
856
|
"""Read and parse hex file.
|
|
836
857
|
|
|
@@ -860,25 +881,23 @@ class Reader(BaseType):
|
|
|
860
881
|
continue
|
|
861
882
|
|
|
862
883
|
matched = True
|
|
863
|
-
|
|
864
|
-
container.line_number = line_number
|
|
865
|
-
dict_ = match.groupdict()
|
|
884
|
+
dict_: dict[str, Optional[str]] = match.groupdict()
|
|
866
885
|
|
|
867
886
|
if not dict_:
|
|
868
887
|
continue
|
|
869
888
|
|
|
870
889
|
self.stats.record_types[format_type] += 1
|
|
871
890
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
setattr(container, key, value)
|
|
891
|
+
address = self._parse_optional_int(dict_, "address")
|
|
892
|
+
length = self._parse_optional_int(dict_, "length")
|
|
893
|
+
record_type = self._parse_optional_int(dict_, "type")
|
|
894
|
+
checksum = self._parse_optional_int(dict_, "checksum")
|
|
895
|
+
junk = dict_.get("junk")
|
|
878
896
|
|
|
879
897
|
# Parse data chunk
|
|
880
|
-
|
|
881
|
-
|
|
898
|
+
chunk: Optional[bytearray] = None
|
|
899
|
+
chunk_str = dict_.get("chunk")
|
|
900
|
+
if chunk_str is not None:
|
|
882
901
|
if self.DATA_SEPARATOR:
|
|
883
902
|
chunk_str = chunk_str.replace(self.DATA_SEPARATOR, "")
|
|
884
903
|
|
|
@@ -886,7 +905,16 @@ class Reader(BaseType):
|
|
|
886
905
|
if chunk_str:
|
|
887
906
|
for idx in range(0, len(chunk_str), 2):
|
|
888
907
|
chunk.append(atoi(chunk_str[idx : idx + 2]))
|
|
889
|
-
|
|
908
|
+
|
|
909
|
+
container = Container(
|
|
910
|
+
line_number=line_number,
|
|
911
|
+
address=address,
|
|
912
|
+
length=length,
|
|
913
|
+
type=record_type,
|
|
914
|
+
checksum=checksum,
|
|
915
|
+
chunk=chunk,
|
|
916
|
+
junk=junk,
|
|
917
|
+
)
|
|
890
918
|
|
|
891
919
|
# Validate line
|
|
892
920
|
self.check_line(container, format_type)
|
|
@@ -894,8 +922,8 @@ class Reader(BaseType):
|
|
|
894
922
|
# Process data lines
|
|
895
923
|
if self.is_data_line(container, format_type):
|
|
896
924
|
if self.parseData(container, format_type):
|
|
897
|
-
address =
|
|
898
|
-
chunk =
|
|
925
|
+
address = (container.address if container.address is not None else 0) + self.base_address
|
|
926
|
+
chunk = container.chunk if container.chunk is not None else bytearray()
|
|
899
927
|
self.stats.data_bytes[format_type] += len(chunk)
|
|
900
928
|
section = Section(address, chunk)
|
|
901
929
|
sections.append(section)
|
|
@@ -908,8 +936,8 @@ class Reader(BaseType):
|
|
|
908
936
|
meta_data[format_type].append(
|
|
909
937
|
MetaRecord(
|
|
910
938
|
format_type=format_type,
|
|
911
|
-
address=
|
|
912
|
-
chunk=
|
|
939
|
+
address=container.address,
|
|
940
|
+
chunk=container.chunk,
|
|
913
941
|
)
|
|
914
942
|
)
|
|
915
943
|
break # Pattern matched, stop trying formats
|
|
@@ -56,19 +56,6 @@ class Reader(hexfile.Reader):
|
|
|
56
56
|
def __init__(self) -> None:
|
|
57
57
|
"""Initialize reader with address calculation state."""
|
|
58
58
|
super().__init__()
|
|
59
|
-
self.segmentAddress: int = 0
|
|
60
|
-
self._address_calculator: Callable[[int], int] = self._default_address_calculator
|
|
61
|
-
|
|
62
|
-
def _default_address_calculator(self, x: int) -> int:
|
|
63
|
-
"""Default address calculator (identity function).
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
x: Input address
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Same address unchanged
|
|
70
|
-
"""
|
|
71
|
-
return x
|
|
72
59
|
|
|
73
60
|
def check_line(self, line: Any, format_type: int) -> None:
|
|
74
61
|
"""Validate Intel HEX record checksum.
|
|
@@ -106,7 +93,7 @@ class Reader(hexfile.Reader):
|
|
|
106
93
|
"""
|
|
107
94
|
return line.type == DATA
|
|
108
95
|
|
|
109
|
-
def
|
|
96
|
+
def calculate_base_address(self, line: Any, shift_by: int, name: str) -> None:
|
|
110
97
|
"""Calculate extended address from segment/linear address record.
|
|
111
98
|
|
|
112
99
|
Args:
|
|
@@ -118,9 +105,7 @@ class Reader(hexfile.Reader):
|
|
|
118
105
|
# Extract 16-bit segment value
|
|
119
106
|
segment = (line.chunk[0] << 8) | line.chunk[1]
|
|
120
107
|
line.add_processing_instruction(("segment", segment))
|
|
121
|
-
|
|
122
|
-
# Update address calculator to add segment base
|
|
123
|
-
self._address_calculator = partial(operator.add, segment << shift_by)
|
|
108
|
+
self.base_address = segment << shift_by
|
|
124
109
|
self.debug(f"EXTENDED_{name.upper()}_ADDRESS: {segment:#X}")
|
|
125
110
|
else:
|
|
126
111
|
self.error(f"Bad Extended {name} Address at line #{line.line_number}.")
|
|
@@ -135,43 +120,36 @@ class Reader(hexfile.Reader):
|
|
|
135
120
|
Note:
|
|
136
121
|
Handles extended addressing, start addresses, and EOF records.
|
|
137
122
|
"""
|
|
138
|
-
if line.type ==
|
|
139
|
-
# Apply extended address calculation
|
|
140
|
-
line.address = self._address_calculator(line.address)
|
|
141
|
-
|
|
142
|
-
elif line.type == EXTENDED_SEGMENT_ADDRESS:
|
|
123
|
+
if line.type == EXTENDED_SEGMENT_ADDRESS:
|
|
143
124
|
# Extended segment address (20-bit: segment << 4)
|
|
144
|
-
self.
|
|
145
|
-
|
|
125
|
+
self.calculate_base_address(line, 4, "Segment")
|
|
146
126
|
elif line.type == START_SEGMENT_ADDRESS:
|
|
147
127
|
# Start segment address (CS:IP for x86)
|
|
148
128
|
if len(line.chunk) == 4:
|
|
149
129
|
cs = (line.chunk[0] << 8) | line.chunk[1]
|
|
150
130
|
ip = (line.chunk[2] << 8) | line.chunk[3]
|
|
131
|
+
self.base_address = (cs << 4) + ip
|
|
151
132
|
line.add_processing_instruction(("cs", cs))
|
|
152
133
|
line.add_processing_instruction(("ip", ip))
|
|
153
134
|
self.debug(f"START_SEGMENT_ADDRESS: {hex(cs)}:{hex(ip)}")
|
|
154
135
|
else:
|
|
155
136
|
self.error(f"Bad Segment Address at line #{line.line_number}.")
|
|
156
|
-
|
|
157
137
|
elif line.type == EXTENDED_LINEAR_ADDRESS:
|
|
158
138
|
# Extended linear address (32-bit: segment << 16)
|
|
159
|
-
self.
|
|
160
|
-
|
|
139
|
+
self.calculate_base_address(line, 16, "Linear")
|
|
161
140
|
elif line.type == START_LINEAR_ADDRESS:
|
|
162
141
|
# Start linear address (EIP for x86)
|
|
163
142
|
if len(line.chunk) == 4:
|
|
164
143
|
eip = (line.chunk[0] << 24) | (line.chunk[1] << 16) | (line.chunk[2] << 8) | line.chunk[3]
|
|
144
|
+
self.base_address = eip
|
|
165
145
|
line.add_processing_instruction(("eip", eip))
|
|
166
146
|
self.debug(f"START_LINEAR_ADDRESS: {hex(eip)}")
|
|
167
147
|
else:
|
|
168
148
|
self.error(f"Bad Linear Address at line #{line.line_number}.")
|
|
169
|
-
|
|
170
149
|
elif line.type == EOF:
|
|
171
150
|
# End of file - nothing to process
|
|
172
151
|
pass
|
|
173
|
-
|
|
174
|
-
else:
|
|
152
|
+
elif line.type != DATA:
|
|
175
153
|
self.warn(f"Invalid record type [{line.type}] at line {line.line_number}")
|
|
176
154
|
|
|
177
155
|
|
|
@@ -640,6 +640,28 @@ class Image:
|
|
|
640
640
|
"""Write an ASAM string datatype (ASCII/UTF8/UTF16/UTF32)."""
|
|
641
641
|
self._call_address_function("write_asam_string", addr, value, dtype, **kws)
|
|
642
642
|
|
|
643
|
+
def read_asam_numeric_array(
|
|
644
|
+
self,
|
|
645
|
+
addr: int,
|
|
646
|
+
length: int,
|
|
647
|
+
dtype: str,
|
|
648
|
+
byte_order: str = "MSB_LAST",
|
|
649
|
+
**kws: Any,
|
|
650
|
+
) -> list[Union[int, float]]:
|
|
651
|
+
"""Read an ASAM numeric array with ECU byte order semantics."""
|
|
652
|
+
return self._call_address_function("read_asam_numeric_array", addr, length, dtype, byte_order, **kws)
|
|
653
|
+
|
|
654
|
+
def write_asam_numeric_array(
|
|
655
|
+
self,
|
|
656
|
+
addr: int,
|
|
657
|
+
data: Iterable[Union[int, float]],
|
|
658
|
+
dtype: str,
|
|
659
|
+
byte_order: str = "MSB_LAST",
|
|
660
|
+
**kws: Any,
|
|
661
|
+
) -> None:
|
|
662
|
+
"""Write an ASAM numeric array with ECU byte order semantics."""
|
|
663
|
+
self._call_address_function("write_asam_numeric_array", addr, data, dtype, byte_order, **kws)
|
|
664
|
+
|
|
643
665
|
def read_numeric_array(self, addr: int, length: int, dtype: str, **kws: Any) -> list[Union[int, float]]:
|
|
644
666
|
"""Read array of numeric values from image.
|
|
645
667
|
|
|
@@ -688,6 +710,18 @@ class Image:
|
|
|
688
710
|
"""
|
|
689
711
|
self._call_address_function("write_ndarray", addr, array, order=order, **kws)
|
|
690
712
|
|
|
713
|
+
def write_asam_ndarray(
|
|
714
|
+
self,
|
|
715
|
+
addr: int,
|
|
716
|
+
array: Any,
|
|
717
|
+
dtype: str,
|
|
718
|
+
byte_order: str = "MSB_LAST",
|
|
719
|
+
order: Optional[str] = None,
|
|
720
|
+
**kws: Any,
|
|
721
|
+
) -> None:
|
|
722
|
+
"""Write a NumPy ndarray using ASAM datatype and ECU byte order semantics."""
|
|
723
|
+
self._call_address_function("write_asam_ndarray", addr, array, dtype, byte_order, order=order, **kws)
|
|
724
|
+
|
|
691
725
|
def read_ndarray(
|
|
692
726
|
self,
|
|
693
727
|
addr: int,
|
|
@@ -715,6 +749,19 @@ class Image:
|
|
|
715
749
|
"""
|
|
716
750
|
return self._call_address_function("read_ndarray", addr, length, dtype, shape, order, **kws)
|
|
717
751
|
|
|
752
|
+
def read_asam_ndarray(
|
|
753
|
+
self,
|
|
754
|
+
addr: int,
|
|
755
|
+
length: int,
|
|
756
|
+
dtype: str,
|
|
757
|
+
shape: Optional[tuple[int, ...]] = None,
|
|
758
|
+
order: Optional[str] = None,
|
|
759
|
+
byte_order: str = "MSB_LAST",
|
|
760
|
+
**kws: Any,
|
|
761
|
+
) -> Any:
|
|
762
|
+
"""Read a NumPy ndarray using ASAM datatype and ECU byte order semantics."""
|
|
763
|
+
return self._call_address_function("read_asam_ndarray", addr, length, dtype, shape, order, byte_order, **kws)
|
|
764
|
+
|
|
718
765
|
def read_string(self, addr: int, encoding: str = "latin1", length: int = -1, **kws: Any) -> str:
|
|
719
766
|
"""Read string from image.
|
|
720
767
|
|
|
@@ -54,14 +54,6 @@ def main():
|
|
|
54
54
|
default=False,
|
|
55
55
|
help="Print filename including path",
|
|
56
56
|
)
|
|
57
|
-
parser.add_argument(
|
|
58
|
-
"-j",
|
|
59
|
-
"--join-sections",
|
|
60
|
-
dest="join_section",
|
|
61
|
-
action="store_true",
|
|
62
|
-
default=False,
|
|
63
|
-
help="Join adjacent sections",
|
|
64
|
-
)
|
|
65
57
|
|
|
66
58
|
args = parser.parse_args()
|
|
67
59
|
|
|
@@ -88,7 +80,7 @@ def main():
|
|
|
88
80
|
print(f"Could not determine file type for '{hex_file}'.")
|
|
89
81
|
sys.exit(1)
|
|
90
82
|
|
|
91
|
-
img = load(file_type.lower(), hex_file
|
|
83
|
+
img = load(file_type.lower(), hex_file)
|
|
92
84
|
if args.print_filename:
|
|
93
85
|
print(f"\nFile: {hex_file}")
|
|
94
86
|
print("\nSections")
|
|
@@ -32,6 +32,9 @@ Architecture
|
|
|
32
32
|
├── read/write(addr, length) # Raw bytes
|
|
33
33
|
├── read_numeric/write_numeric(addr, dtype) # Single values
|
|
34
34
|
├── read_numeric_array/write_numeric_array() # Arrays
|
|
35
|
+
├── read_asam_numeric/write_asam_numeric() # ASAM scalars
|
|
36
|
+
├── read_asam_numeric_array/write_asam_numeric_array() # ASAM arrays
|
|
37
|
+
├── read_asam_ndarray/write_asam_ndarray() # ASAM NumPy arrays
|
|
35
38
|
├── read_string/write_string() # Null-terminated strings
|
|
36
39
|
└── read_ndarray/write_ndarray() # NumPy arrays
|
|
37
40
|
|
|
@@ -587,10 +590,24 @@ class Section:
|
|
|
587
590
|
internal_dtype = ASAM_NUMERIC_DTYPES.get(normalized_dtype)
|
|
588
591
|
if internal_dtype is None:
|
|
589
592
|
raise TypeError(f"Unsupported ASAM datatype {asam_dtype!r}")
|
|
590
|
-
if internal_dtype in ("uint8", "int8"):
|
|
591
|
-
return internal_dtype
|
|
592
593
|
return f"{internal_dtype}_{ASAM_ENDIAN_FOR_BYTEORDER[asam_byte_order]}"
|
|
593
594
|
|
|
595
|
+
def _numpy_dtype_from_internal(self, dtype: str) -> np.dtype:
|
|
596
|
+
type_, byte_order = self._verify_dtype(dtype)
|
|
597
|
+
return np.dtype(type_).newbyteorder(BYTEORDER[byte_order])
|
|
598
|
+
|
|
599
|
+
def _permute_asam_buffer(self, data: bytes, internal_dtype: str, byte_order: str) -> bytes:
|
|
600
|
+
type_name = internal_dtype.split("_")[0]
|
|
601
|
+
element_size = TYPE_SIZES.get(type_name, 0)
|
|
602
|
+
if element_size <= 1 or not data:
|
|
603
|
+
return data
|
|
604
|
+
if len(data) % element_size != 0:
|
|
605
|
+
raise ValueError("ASAM buffer length must be a multiple of the element size")
|
|
606
|
+
return b"".join(
|
|
607
|
+
self._permute_asam_bytes_for_read(data[idx : idx + element_size], byte_order)
|
|
608
|
+
for idx in range(0, len(data), element_size)
|
|
609
|
+
)
|
|
610
|
+
|
|
594
611
|
def read(self, addr: int, length: int, **kws) -> bytes:
|
|
595
612
|
"""Read raw bytes from section at specified address.
|
|
596
613
|
|
|
@@ -763,7 +780,7 @@ class Section:
|
|
|
763
780
|
|
|
764
781
|
fmt = self._getformat(internal_dtype)
|
|
765
782
|
packed = struct.pack(fmt, value)
|
|
766
|
-
permuted = self.
|
|
783
|
+
permuted = self._permute_asam_buffer(packed, internal_dtype, asam_byte_order)
|
|
767
784
|
return struct.unpack(fmt, permuted)[0]
|
|
768
785
|
|
|
769
786
|
def write_asam_numeric(
|
|
@@ -782,9 +799,44 @@ class Section:
|
|
|
782
799
|
|
|
783
800
|
fmt = self._getformat(internal_dtype)
|
|
784
801
|
packed = struct.pack(fmt, value)
|
|
785
|
-
permuted = self.
|
|
802
|
+
permuted = self._permute_asam_buffer(packed, internal_dtype, asam_byte_order)
|
|
786
803
|
self.write(addr, permuted)
|
|
787
804
|
|
|
805
|
+
def read_asam_numeric_array(
|
|
806
|
+
self,
|
|
807
|
+
addr: int,
|
|
808
|
+
length: int,
|
|
809
|
+
dtype: str,
|
|
810
|
+
byte_order: str = "MSB_LAST",
|
|
811
|
+
**kws,
|
|
812
|
+
) -> Union[tuple[int, ...], tuple[float, ...]]:
|
|
813
|
+
asam_byte_order = self._resolve_asam_byteorder(byte_order)
|
|
814
|
+
internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
|
|
815
|
+
fmt = self._getformat(internal_dtype, length)
|
|
816
|
+
data_size = struct.calcsize(fmt)
|
|
817
|
+
raw_data = self.read(addr, data_size, **kws)
|
|
818
|
+
permuted = self._permute_asam_buffer(raw_data, internal_dtype, asam_byte_order)
|
|
819
|
+
return struct.unpack(fmt, permuted)
|
|
820
|
+
|
|
821
|
+
def write_asam_numeric_array(
|
|
822
|
+
self,
|
|
823
|
+
addr: int,
|
|
824
|
+
data: Union[list[int], list[float]],
|
|
825
|
+
dtype: str,
|
|
826
|
+
byte_order: str = "MSB_LAST",
|
|
827
|
+
**kws,
|
|
828
|
+
) -> None:
|
|
829
|
+
if not hasattr(data, "__iter__"):
|
|
830
|
+
raise TypeError("data must be iterable")
|
|
831
|
+
|
|
832
|
+
values = tuple(data)
|
|
833
|
+
asam_byte_order = self._resolve_asam_byteorder(byte_order)
|
|
834
|
+
internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
|
|
835
|
+
fmt = self._getformat(internal_dtype, len(values))
|
|
836
|
+
packed = struct.pack(fmt, *values)
|
|
837
|
+
permuted = self._permute_asam_buffer(packed, internal_dtype, asam_byte_order)
|
|
838
|
+
self.write(addr, permuted, **kws)
|
|
839
|
+
|
|
788
840
|
def read_asam_string(self, addr: int, dtype: str, length: int = -1, **kws) -> str:
|
|
789
841
|
encoding = ASAM_STRING_ENCODINGS.get(dtype.strip().upper())
|
|
790
842
|
if encoding is None:
|
|
@@ -920,6 +972,28 @@ class Section:
|
|
|
920
972
|
else:
|
|
921
973
|
self.data[offset : offset + data_size] = array.tobytes()
|
|
922
974
|
|
|
975
|
+
def write_asam_ndarray(
|
|
976
|
+
self,
|
|
977
|
+
addr: int,
|
|
978
|
+
array: np.ndarray,
|
|
979
|
+
dtype: str,
|
|
980
|
+
byte_order: str = "MSB_LAST",
|
|
981
|
+
order: str = None,
|
|
982
|
+
**kws,
|
|
983
|
+
) -> None:
|
|
984
|
+
if not isinstance(array, np.ndarray):
|
|
985
|
+
raise TypeError("array must be of type numpy.ndarray.")
|
|
986
|
+
|
|
987
|
+
asam_byte_order = self._resolve_asam_byteorder(byte_order)
|
|
988
|
+
internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
|
|
989
|
+
typed_array = np.asarray(array, dtype=self._numpy_dtype_from_internal(internal_dtype))
|
|
990
|
+
if order is not None and order == "F":
|
|
991
|
+
raw_data = fortran_array_to_buffer(array=typed_array)
|
|
992
|
+
else:
|
|
993
|
+
raw_data = typed_array.tobytes()
|
|
994
|
+
permuted = self._permute_asam_buffer(raw_data, internal_dtype, asam_byte_order)
|
|
995
|
+
self.write(addr, permuted, **kws)
|
|
996
|
+
|
|
923
997
|
def read_ndarray(self, addr: int, length: int, dtype: str, shape: tuple = None, order: str = None, **kws) -> np.ndarray:
|
|
924
998
|
""" """
|
|
925
999
|
offset = addr - self.start_address
|
|
@@ -927,9 +1001,7 @@ class Section:
|
|
|
927
1001
|
raise InvalidAddressError(f"read_ndarray(0x{addr:08x}) access out of bounds.")
|
|
928
1002
|
if offset + length > self.length:
|
|
929
1003
|
raise InvalidAddressError(f"read_ndarray(0x{addr:08x}) access out of bounds.")
|
|
930
|
-
|
|
931
|
-
dt = np.dtype(type_)
|
|
932
|
-
dt = dt.newbyteorder(BYTEORDER.get(byte_order))
|
|
1004
|
+
dt = self._numpy_dtype_from_internal(dtype)
|
|
933
1005
|
if order is not None and order == "F":
|
|
934
1006
|
arr = fortran_array_from_buffer(arr=self.data[offset : offset + length], shape=shape, dtype=dt)
|
|
935
1007
|
else:
|
|
@@ -937,6 +1009,27 @@ class Section:
|
|
|
937
1009
|
arr = flat.reshape(shape) if shape else flat
|
|
938
1010
|
return arr
|
|
939
1011
|
|
|
1012
|
+
def read_asam_ndarray(
|
|
1013
|
+
self,
|
|
1014
|
+
addr: int,
|
|
1015
|
+
length: int,
|
|
1016
|
+
dtype: str,
|
|
1017
|
+
shape: tuple = None,
|
|
1018
|
+
order: str = None,
|
|
1019
|
+
byte_order: str = "MSB_LAST",
|
|
1020
|
+
**kws,
|
|
1021
|
+
) -> np.ndarray:
|
|
1022
|
+
asam_byte_order = self._resolve_asam_byteorder(byte_order)
|
|
1023
|
+
internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
|
|
1024
|
+
dt = self._numpy_dtype_from_internal(internal_dtype)
|
|
1025
|
+
raw_data = self.read(addr, length, **kws)
|
|
1026
|
+
permuted = self._permute_asam_buffer(raw_data, internal_dtype, asam_byte_order)
|
|
1027
|
+
if order is not None and order == "F":
|
|
1028
|
+
return fortran_array_from_buffer(arr=permuted, shape=shape, dtype=dt)
|
|
1029
|
+
|
|
1030
|
+
flat = np.frombuffer(permuted, dtype=dt)
|
|
1031
|
+
return flat.reshape(shape) if shape else flat
|
|
1032
|
+
|
|
940
1033
|
"""
|
|
941
1034
|
def write_timestamp():
|
|
942
1035
|
pass
|
|
@@ -1029,11 +1122,10 @@ class LazySection(Section):
|
|
|
1029
1122
|
|
|
1030
1123
|
|
|
1031
1124
|
def join_sections(sections: list[Section]) -> list[Section]:
|
|
1032
|
-
"""Join
|
|
1125
|
+
"""Join sections when their address ranges are compatible.
|
|
1033
1126
|
|
|
1034
|
-
Merges sections that are adjacent in memory
|
|
1035
|
-
|
|
1036
|
-
sections remain separate.
|
|
1127
|
+
Merges sections that are adjacent in memory or overlap with identical bytes in
|
|
1128
|
+
the shared range. Conflicting overlaps remain separate sections.
|
|
1037
1129
|
|
|
1038
1130
|
The function sorts sections by start address before processing.
|
|
1039
1131
|
|
|
@@ -1041,7 +1133,7 @@ def join_sections(sections: list[Section]) -> list[Section]:
|
|
|
1041
1133
|
sections: List of Section objects to join
|
|
1042
1134
|
|
|
1043
1135
|
Returns:
|
|
1044
|
-
New list of Section objects with
|
|
1136
|
+
New list of Section objects with compatible sections merged
|
|
1045
1137
|
|
|
1046
1138
|
Raises:
|
|
1047
1139
|
TypeError: If any element is not a Section instance
|
|
@@ -1068,20 +1160,46 @@ def join_sections(sections: list[Section]) -> list[Section]:
|
|
|
1068
1160
|
This function is automatically called when creating an Image with
|
|
1069
1161
|
``join=True`` parameter.
|
|
1070
1162
|
"""
|
|
1071
|
-
result_sections = []
|
|
1072
|
-
sections.sort(key=attrgetter("start_address"))
|
|
1073
|
-
prev_section = Section()
|
|
1163
|
+
result_sections: list[Section] = []
|
|
1074
1164
|
for section in sections:
|
|
1075
1165
|
if not isinstance(section, Section):
|
|
1076
1166
|
raise TypeError("'{}' is not a 'Section' instance", section)
|
|
1077
|
-
|
|
1078
|
-
|
|
1167
|
+
|
|
1168
|
+
sorted_sections = sorted(sections, key=attrgetter("start_address"))
|
|
1169
|
+
|
|
1170
|
+
for section in sorted_sections:
|
|
1171
|
+
|
|
1172
|
+
if not result_sections:
|
|
1173
|
+
result_sections.append(Section(section.start_address, section.data))
|
|
1174
|
+
continue
|
|
1175
|
+
|
|
1176
|
+
last_segment = result_sections[-1]
|
|
1177
|
+
last_end = last_segment.start_address + last_segment.length
|
|
1178
|
+
section_start = section.start_address
|
|
1179
|
+
section_end = section_start + section.length
|
|
1180
|
+
|
|
1181
|
+
if section_start > last_end:
|
|
1182
|
+
result_sections.append(Section(section.start_address, section.data))
|
|
1183
|
+
continue
|
|
1184
|
+
|
|
1185
|
+
if section_start == last_end:
|
|
1079
1186
|
last_segment.data.extend(section.data)
|
|
1080
|
-
|
|
1081
|
-
|
|
1187
|
+
continue
|
|
1188
|
+
|
|
1189
|
+
overlap_start = section_start
|
|
1190
|
+
overlap_end = min(last_end, section_end)
|
|
1191
|
+
overlap_length = overlap_end - overlap_start
|
|
1192
|
+
last_offset = overlap_start - last_segment.start_address
|
|
1193
|
+
section_offset = overlap_start - section_start
|
|
1194
|
+
|
|
1195
|
+
last_overlap = last_segment.data[last_offset : last_offset + overlap_length]
|
|
1196
|
+
section_overlap = section.data[section_offset : section_offset + overlap_length]
|
|
1197
|
+
if last_overlap != section_overlap:
|
|
1082
1198
|
result_sections.append(Section(section.start_address, section.data))
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1199
|
+
continue
|
|
1200
|
+
|
|
1201
|
+
if section_end > last_end:
|
|
1202
|
+
tail_offset = overlap_end - section_start
|
|
1203
|
+
last_segment.data.extend(section.data[tail_offset:])
|
|
1204
|
+
|
|
1205
|
+
return result_sections
|