objutils 0.10.3__tar.gz → 0.10.4__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.
Files changed (107) hide show
  1. {objutils-0.10.3 → objutils-0.10.4}/PKG-INFO +6 -2
  2. {objutils-0.10.3 → objutils-0.10.4}/docs/README.rst +5 -1
  3. {objutils-0.10.3 → objutils-0.10.4}/objutils/__init__.py +1 -1
  4. {objutils-0.10.3 → objutils-0.10.4}/objutils/cosmac.py +4 -7
  5. {objutils-0.10.3 → objutils-0.10.4}/objutils/elf/model.py +7 -2
  6. {objutils-0.10.3 → objutils-0.10.4}/objutils/emon52.py +1 -3
  7. {objutils-0.10.3 → objutils-0.10.4}/objutils/etek.py +4 -10
  8. {objutils-0.10.3 → objutils-0.10.4}/objutils/hexfile.py +1 -1
  9. {objutils-0.10.3 → objutils-0.10.4}/objutils/ihex.py +1 -2
  10. {objutils-0.10.3 → objutils-0.10.4}/objutils/image.py +30 -6
  11. {objutils-0.10.3 → objutils-0.10.4}/objutils/pecoff/__init__.py +12 -12
  12. {objutils-0.10.3 → objutils-0.10.4}/objutils/pecoff/model.py +4 -1
  13. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_coff_extract.py +26 -2
  14. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_coff_syms.py +2 -2
  15. {objutils-0.10.3 → objutils-0.10.4}/objutils/section.py +150 -8
  16. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_etek.py +3 -5
  17. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_image.py +2 -2
  18. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_section.py +339 -169
  19. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_section_join.py +0 -1
  20. {objutils-0.10.3 → objutils-0.10.4}/objutils/version.py +1 -1
  21. {objutils-0.10.3 → objutils-0.10.4}/pyproject.toml +2 -2
  22. {objutils-0.10.3 → objutils-0.10.4}/CMakeLists.txt +0 -0
  23. {objutils-0.10.3 → objutils-0.10.4}/LICENSE +0 -0
  24. {objutils-0.10.3 → objutils-0.10.4}/build_ext.py +0 -0
  25. {objutils-0.10.3 → objutils-0.10.4}/objutils/.coveragerc +0 -0
  26. {objutils-0.10.3 → objutils-0.10.4}/objutils/.vscode/launch.json +0 -0
  27. {objutils-0.10.3 → objutils-0.10.4}/objutils/.vscode/settings.json +0 -0
  28. {objutils-0.10.3 → objutils-0.10.4}/objutils/a2l_test.shf +0 -0
  29. {objutils-0.10.3 → objutils-0.10.4}/objutils/ash.py +0 -0
  30. {objutils-0.10.3 → objutils-0.10.4}/objutils/binfile.py +0 -0
  31. {objutils-0.10.3 → objutils-0.10.4}/objutils/checksums.py +0 -0
  32. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/.traverser.py.un~ +0 -0
  33. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/__init__.py +0 -0
  34. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/attrparser.py +0 -0
  35. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/c_generator.py +0 -0
  36. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/constants.py +0 -0
  37. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/encoding.py +0 -0
  38. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/lineprog.py +0 -0
  39. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/readers.py +0 -0
  40. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/sm.py +0 -0
  41. {objutils-0.10.3 → objutils-0.10.4}/objutils/dwarf/traverser.py +0 -0
  42. {objutils-0.10.3 → objutils-0.10.4}/objutils/elf/__init__.py +0 -0
  43. {objutils-0.10.3 → objutils-0.10.4}/objutils/elf/arm/__init__.py +0 -0
  44. {objutils-0.10.3 → objutils-0.10.4}/objutils/elf/arm/attributes.py +0 -0
  45. {objutils-0.10.3 → objutils-0.10.4}/objutils/elf/defs.py +0 -0
  46. {objutils-0.10.3 → objutils-0.10.4}/objutils/exceptions.py +0 -0
  47. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/__init__.py +0 -0
  48. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/ctre.hpp +0 -0
  49. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/difflib.h +0 -0
  50. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/exceptions.cpp +0 -0
  51. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/exceptions.hpp +0 -0
  52. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/hexfile.cpp +0 -0
  53. {objutils-0.10.3 → objutils-0.10.4}/objutils/extensions/wrapper.cpp +0 -0
  54. {objutils-0.10.3 → objutils-0.10.4}/objutils/fpc.py +0 -0
  55. {objutils-0.10.3 → objutils-0.10.4}/objutils/hexdump.py +0 -0
  56. {objutils-0.10.3 → objutils-0.10.4}/objutils/ieee695.py +0 -0
  57. {objutils-0.10.3 → objutils-0.10.4}/objutils/logger.py +0 -0
  58. {objutils-0.10.3 → objutils-0.10.4}/objutils/mostec.py +0 -0
  59. {objutils-0.10.3 → objutils-0.10.4}/objutils/objutils.code-workspace +0 -0
  60. {objutils-0.10.3 → objutils-0.10.4}/objutils/pecoff/defs.py +0 -0
  61. {objutils-0.10.3 → objutils-0.10.4}/objutils/pecoff/pdb/__init__.py +0 -0
  62. {objutils-0.10.3 → objutils-0.10.4}/objutils/pickleif.py +0 -0
  63. {objutils-0.10.3 → objutils-0.10.4}/objutils/rca.py +0 -0
  64. {objutils-0.10.3 → objutils-0.10.4}/objutils/readers.py +0 -0
  65. {objutils-0.10.3 → objutils-0.10.4}/objutils/registry.py +0 -0
  66. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/arduino_build_artifacts.py +0 -0
  67. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_cgen.py +0 -0
  68. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_coff_import.py +0 -0
  69. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_coff_info.py +0 -0
  70. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_dwarf_import.py +0 -0
  71. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_elf_arm_attrs.py +0 -0
  72. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_elf_extract.py +0 -0
  73. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_elf_import.py +0 -0
  74. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_elf_info.py +0 -0
  75. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_elf_syms.py +0 -0
  76. {objutils-0.10.3 → objutils-0.10.4}/objutils/scripts/oj_hex_info.py +0 -0
  77. {objutils-0.10.3 → objutils-0.10.4}/objutils/shf.py +0 -0
  78. {objutils-0.10.3 → objutils-0.10.4}/objutils/sig.py +0 -0
  79. {objutils-0.10.3 → objutils-0.10.4}/objutils/srec.py +0 -0
  80. {objutils-0.10.3 → objutils-0.10.4}/objutils/tek.py +0 -0
  81. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/__init__.py +0 -0
  82. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_arm_attributes.py +0 -0
  83. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_ash.py +0 -0
  84. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_c_generator.py +0 -0
  85. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_checksums.py +0 -0
  86. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_cygpath.py +0 -0
  87. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_diff_bin.py +0 -0
  88. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_elf.py +0 -0
  89. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_emon52.py +0 -0
  90. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_examples_cgen.py +0 -0
  91. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_fpc.py +0 -0
  92. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_hexdump.py +0 -0
  93. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_hexfile.py +0 -0
  94. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_ihex.py +0 -0
  95. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_mostec.py +0 -0
  96. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_readers.py +0 -0
  97. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_registry.py +0 -0
  98. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_repr.py +0 -0
  99. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_shf.py +0 -0
  100. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_sm.py +0 -0
  101. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_srec.py +0 -0
  102. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_tek.py +0 -0
  103. {objutils-0.10.3 → objutils-0.10.4}/objutils/tests/test_titext.py +0 -0
  104. {objutils-0.10.3 → objutils-0.10.4}/objutils/titxt.py +0 -0
  105. {objutils-0.10.3 → objutils-0.10.4}/objutils/utils/__init__.py +0 -0
  106. {objutils-0.10.3 → objutils-0.10.4}/objutils/utils/arduino.py +0 -0
  107. {objutils-0.10.3 → objutils-0.10.4}/objutils/utils/diff.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: objutils
3
- Version: 0.10.3
3
+ Version: 0.10.4
4
4
  Summary: Objectfile library for Python
5
5
  License: GPLv2
6
6
  License-File: LICENSE
@@ -36,7 +36,7 @@ Readme
36
36
  .. image:: https://github.com/christoph2/objutils/raw/master/docs/objutils_banner.png
37
37
  :align: center
38
38
 
39
- |PyPI| |Python Versions| |License: GPLv2| |Code style: black|
39
+ |PyPI| |Python Versions| |License: GPLv2| |Code style: black| |Ask DeepWiki| |PDF Manual|
40
40
 
41
41
 
42
42
  Binary data stored in hex-files is in widespread use especially in embedded systems applications.
@@ -467,4 +467,8 @@ If you contribute code to this project, you are implicitly allowing your code to
467
467
  :target: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
468
468
  .. |Code style: black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
469
469
  :target: https://github.com/psf/black
470
+ .. |Ask DeepWiki| image:: https://deepwiki.com/badge.svg
471
+ :target: https://deepwiki.com/christoph2/objutils
472
+ .. |PDF Manual| image:: https://img.shields.io/badge/docs-PDF%20manual-blue.svg
473
+ :target: https://objutils.readthedocs.io/_/downloads/en/latest/pdf/
470
474
 
@@ -5,7 +5,7 @@ Readme
5
5
  .. image:: https://github.com/christoph2/objutils/raw/master/docs/objutils_banner.png
6
6
  :align: center
7
7
 
8
- |PyPI| |Python Versions| |License: GPLv2| |Code style: black|
8
+ |PyPI| |Python Versions| |License: GPLv2| |Code style: black| |Ask DeepWiki| |PDF Manual|
9
9
 
10
10
 
11
11
  Binary data stored in hex-files is in widespread use especially in embedded systems applications.
@@ -436,3 +436,7 @@ If you contribute code to this project, you are implicitly allowing your code to
436
436
  :target: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
437
437
  .. |Code style: black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
438
438
  :target: https://github.com/psf/black
439
+ .. |Ask DeepWiki| image:: https://deepwiki.com/badge.svg
440
+ :target: https://deepwiki.com/christoph2/objutils
441
+ .. |PDF Manual| image:: https://img.shields.io/badge/docs-PDF%20manual-blue.svg
442
+ :target: https://objutils.readthedocs.io/_/downloads/en/latest/pdf/
@@ -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.3"
16
+ __version__ = "0.10.4"
17
17
 
18
18
  __all__ = [
19
19
  "Image",
@@ -5,14 +5,11 @@ This module handles the RCA Cosmac hex format, used with RCA 1802/1804/1805
5
5
  microprocessors and COSMAC development systems.
6
6
 
7
7
  Format specification:
8
-
9
8
  - Four data record formats with optional address:
10
-
11
- 1. ``!MAAAA DD`` Full format with start symbol
12
- 2. ``?MAAAA DD`` Alternate start symbol
13
- 3. ``AAAA DD`` Address only (no symbol)
14
- 4. ``DD`` – Data only (address continues from previous)
15
-
9
+ 1. !MAAAA DD - Full format with start symbol
10
+ 2. ?MAAAA DD - Alternate start symbol
11
+ 3. AAAA DD - Address only (no symbol)
12
+ 4. DD - Data only (address continues from previous)
16
13
  - M: Memory identifier (single hex digit)
17
14
  - AAAA: 16-bit address (hex)
18
15
  - DD: Data bytes (hex, space-separated)
@@ -130,7 +130,7 @@ from sqlalchemy.engine import Engine
130
130
  from sqlalchemy.exc import SQLAlchemyError
131
131
  from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
132
132
  from sqlalchemy.orm import Session, declarative_base, relationship
133
- from sqlalchemy.pool import NullPool
133
+ from sqlalchemy.pool import NullPool, StaticPool
134
134
  from sqlalchemy.sql import func
135
135
 
136
136
  from objutils.elf import defs
@@ -1312,6 +1312,11 @@ class Model:
1312
1312
  # else:
1313
1313
  self.dbname = filename
1314
1314
 
1315
+ # NullPool creates a fresh connection each time, which doesn't work for
1316
+ # :memory: SQLite (each connection gets its own empty database).
1317
+ # Use StaticPool for in-memory databases to reuse the same connection.
1318
+ pool_class = StaticPool if self.dbname == ":memory:" else NullPool
1319
+
1315
1320
  self._engine = create_engine(
1316
1321
  f"sqlite:///{self.dbname}",
1317
1322
  echo=debug,
@@ -1319,7 +1324,7 @@ class Model:
1319
1324
  "detect_types": sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
1320
1325
  "timeout": SQLITE_TIMEOUT_SECONDS,
1321
1326
  },
1322
- poolclass=NullPool,
1327
+ poolclass=pool_class,
1323
1328
  native_datetime=True,
1324
1329
  )
1325
1330
 
@@ -5,9 +5,7 @@ This module handles the EMON52 hex format used by the Elektor
5
5
  Electronics EMON52 8052 development system.
6
6
 
7
7
  Format specification:
8
-
9
- - Data records: ``LL AAAA:DD CCCC``
10
-
8
+ - Data records: LL AAAA:DD CCCC
11
9
  - LL: Length/byte count (hex)
12
10
  - AAAA: 16-bit address (hex)
13
11
  - DD: Data bytes (hex, space-separated)
@@ -5,24 +5,18 @@ This module handles the Extended Tektronix hex format, an extension
5
5
  of the standard Tektronix format with 24-bit addressing and symbol support.
6
6
 
7
7
  Format specification:
8
-
9
- - Data records: ``%LL6CCAAAAADD``
10
-
11
- - LL: Length field (``2 * (data_length + 5)`` in hex)
8
+ - Data records: %LL6CCAAAAADD
9
+ - LL: Length field (2 * (data_length + 5) in hex)
12
10
  - 6: Record type identifier
13
11
  - CC: Checksum (nibble sum)
14
12
  - AAAAAA: 24-bit address (hex)
15
13
  - DD: Data bytes (hex)
16
-
17
- - Symbol records: ``%LL3CCU``
18
-
14
+ - Symbol records: %LL3CCU
19
15
  - LL: Length field
20
16
  - 3: Symbol type identifier
21
17
  - CC: Checksum
22
18
  - U: Symbol string (name + address)
23
-
24
- - EOF records: ``%LL8CCAAAAADD``
25
-
19
+ - EOF records: %LL8CCAAAAADD
26
20
  - 8: EOF type identifier
27
21
  """
28
22
 
@@ -791,7 +791,7 @@ class Reader(BaseType):
791
791
  self.stats = Statistics()
792
792
  self.valid = True
793
793
  self.formats: list[tuple[int, re.Pattern[str]]] = []
794
- self.base_address = 0 # Base address for relative addressing (if applicable - mainly Intel HEX)
794
+ self.base_address = 0 # Base address for relative addressing (if applicable - mainly Intel HEX)
795
795
 
796
796
  # Parse FORMAT_SPEC into compiled patterns
797
797
  if isinstance(self.FORMAT_SPEC, str):
@@ -25,10 +25,9 @@ __copyright__ = """
25
25
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
26
  """
27
27
 
28
- import operator
29
28
  from collections.abc import Mapping, Sequence
30
29
  from functools import partial
31
- from typing import Any, Callable, Optional
30
+ from typing import Any, Optional
32
31
 
33
32
  import objutils.checksums as checksums
34
33
  import objutils.hexfile as hexfile
@@ -716,11 +716,20 @@ class Image:
716
716
  array: Any,
717
717
  dtype: str,
718
718
  byte_order: str = "MSB_LAST",
719
- order: Optional[str] = None,
719
+ index_mode: str = "ROW_DIR",
720
720
  **kws: Any,
721
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)
722
+ """Write a NumPy ndarray using ASAM datatype and ECU byte order semantics.
723
+
724
+ Args:
725
+ addr: Absolute memory address to write to.
726
+ array: NumPy ndarray (shape in numpy convention).
727
+ dtype: ASAM datatype name.
728
+ byte_order: ASAM byte order string.
729
+ index_mode: ``"ROW_DIR"`` (default) or ``"COLUMN_DIR"``.
730
+ **kws: Passed through to section method.
731
+ """
732
+ self._call_address_function("write_asam_ndarray", addr, array, dtype, byte_order, index_mode=index_mode, **kws)
724
733
 
725
734
  def read_ndarray(
726
735
  self,
@@ -755,12 +764,27 @@ class Image:
755
764
  length: int,
756
765
  dtype: str,
757
766
  shape: Optional[tuple[int, ...]] = None,
758
- order: Optional[str] = None,
759
767
  byte_order: str = "MSB_LAST",
768
+ index_mode: str = "ROW_DIR",
760
769
  **kws: Any,
761
770
  ) -> 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)
771
+ """Read a NumPy ndarray using ASAM datatype and ECU byte order semantics.
772
+
773
+ Args:
774
+ addr: Start address to read from.
775
+ length: Number of **elements** (not bytes).
776
+ dtype: ASAM datatype name.
777
+ shape: Dimensions in **ASAM** order ``(X, Y, Z, ...)``.
778
+ byte_order: ASAM byte order string.
779
+ index_mode: ``"ROW_DIR"`` (default) or ``"COLUMN_DIR"``.
780
+ **kws: Passed through to section method.
781
+
782
+ Returns:
783
+ NumPy ndarray with shape in numpy convention.
784
+ """
785
+ return self._call_address_function(
786
+ "read_asam_ndarray", addr, length, dtype, shape=shape, byte_order=byte_order, index_mode=index_mode, **kws
787
+ )
764
788
 
765
789
  def read_string(self, addr: int, encoding: str = "latin1", length: int = -1, **kws: Any) -> str:
766
790
  """Read string from image.
@@ -739,12 +739,12 @@ class SectionAPI:
739
739
  def __init__(self, parent: PeParser):
740
740
  self.parent = parent
741
741
 
742
- def fetch(self, name_pattern: str | None = None) -> list[model.Pe_Section]:
743
- self.parent.create_db_on_demand()
744
- if self.parent.db is None:
745
- raise RuntimeError("PE database is not initialized")
746
- with self.parent.db.session() as ses:
747
- q = ses.query(model.Pe_Section)
742
+ def fetch(self, name_pattern: str | None = None) -> list[model.Pe_Section]:
743
+ self.parent.create_db_on_demand()
744
+ if self.parent.db is None:
745
+ raise RuntimeError("PE database is not initialized")
746
+ with self.parent.db.session() as ses:
747
+ q = ses.query(model.Pe_Section)
748
748
  if name_pattern:
749
749
  q = q.filter(model.Pe_Section.name.like(f"%{name_pattern}%"))
750
750
  return q.order_by(model.Pe_Section.vaddr).all()
@@ -754,12 +754,12 @@ class SymbolAPI:
754
754
  def __init__(self, parent: PeParser):
755
755
  self.parent = parent
756
756
 
757
- def fetch(self, name_pattern: str | None = None) -> list[model.Pe_Symbol]:
758
- self.parent.create_db_on_demand()
759
- if self.parent.db is None:
760
- raise RuntimeError("PE database is not initialized")
761
- with self.parent.db.session() as ses:
762
- q = ses.query(model.Pe_Symbol)
757
+ def fetch(self, name_pattern: str | None = None) -> list[model.Pe_Symbol]:
758
+ self.parent.create_db_on_demand()
759
+ if self.parent.db is None:
760
+ raise RuntimeError("PE database is not initialized")
761
+ with self.parent.db.session() as ses:
762
+ q = ses.query(model.Pe_Symbol)
763
763
  if name_pattern:
764
764
  q = q.filter(model.Pe_Symbol.name.like(f"%{name_pattern}%"))
765
765
  return q.order_by(model.Pe_Symbol.value).all()
@@ -114,7 +114,10 @@ def StdInteger(default=0, primary_key=False, unique=False, nullable=False, index
114
114
  count = StdInteger(default=0, index=True)
115
115
  ```
116
116
  """
117
- return Column(Integer, primary_key=primary_key, unique=unique, nullable=nullable, index=index, default=default)
117
+ kw = dict(primary_key=primary_key, unique=unique, nullable=nullable, index=index)
118
+ if not primary_key:
119
+ kw["default"] = default
120
+ return Column(Integer, **kw)
118
121
 
119
122
 
120
123
  class MixInBase:
@@ -52,6 +52,14 @@ def main(argv: list[str] | None = None) -> int:
52
52
  dest="include",
53
53
  default=None,
54
54
  )
55
+ parser.add_argument(
56
+ "-r",
57
+ "--no-image-base",
58
+ help="Use relative virtual addresses (RVAs) instead of absolute addresses. Required for 64-bit PE files "
59
+ "whose image base pushes addresses beyond 32-bit hex format limits.",
60
+ dest="no_image_base",
61
+ action="store_true",
62
+ )
55
63
  parser.add_argument("-n", help="Number of data bytes per line", dest="row_length", default=16, type=int)
56
64
  args = parser.parse_args(argv)
57
65
 
@@ -60,17 +68,33 @@ def main(argv: list[str] | None = None) -> int:
60
68
  except (OSError, ValueError, RuntimeError) as e:
61
69
  print(f"\n'{args.pe_file}' is not valid PE/COFF file. Raised exception: '{repr(e)}'.")
62
70
  return 1
63
- print("\nExtracting from...\n")
71
+
72
+ add_image_base = not args.no_image_base
73
+ image_base = pp.image_base()
74
+ if args.no_image_base:
75
+ print(f"\nUsing relative addresses (image base {image_base:#010x} subtracted).\n")
76
+ else:
77
+ print(f"\nUsing absolute addresses (image base: {image_base:#010x}).\n")
78
+
79
+ print("Extracting from...\n")
64
80
  print("Section Address Length")
65
81
  print("-" * 45)
66
82
  img = pp.create_image(
67
83
  callback=callback,
68
84
  join=args.join,
85
+ add_image_base=add_image_base,
69
86
  exclude_pattern=args.exclude or "",
70
87
  include_pattern=args.include or "",
71
88
  )
72
89
  if img:
73
- dump(args.file_type, args.output_file_name, img, row_length=args.row_length)
90
+ try:
91
+ dump(args.file_type, args.output_file_name, img, row_length=args.row_length)
92
+ except Exception as e:
93
+ if "address too large" in str(e).lower():
94
+ print(f"\nError: {e}")
95
+ print(f"Hint: Try using --no-image-base / -r to use relative addresses (subtract image base {image_base:#010x}).")
96
+ return 1
97
+ raise
74
98
  print(f"HEX image written to: '{args.output_file_name}' [{len(img)} total bytes]")
75
99
  return 0
76
100
 
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import argparse
7
7
 
8
- from objutils.pecoff import PeParser
8
+ from objutils.pecoff import PeParser, SymbolAPI
9
9
 
10
10
 
11
11
  def main(argv: list[str] | None = None) -> int:
@@ -35,7 +35,7 @@ def main(argv: list[str] | None = None) -> int:
35
35
  return 1
36
36
 
37
37
  # Fetch via SymbolAPI to ensure DB is created/reused
38
- syms = pp.SymbolAPI(pp).fetch(name_pattern=args.pattern) # type: ignore[attr-defined]
38
+ syms = SymbolAPI(pp).fetch(name_pattern=args.pattern)
39
39
  # Fallback: if SymbolAPI attr is not present (static type), use direct list
40
40
  if not syms and pp.symbols:
41
41
  syms = [type("_S", (), s) for s in pp.symbols] # quick adapter for printing
@@ -419,6 +419,78 @@ def fortran_array_to_buffer(array: np.ndarray) -> bytearray:
419
419
  return result
420
420
 
421
421
 
422
+ ASAM_INDEX_MODES = {"ROW_DIR", "COLUMN_DIR"}
423
+
424
+
425
+ def _asam_column_dir_from_buffer(data: bytes, numpy_shape: tuple, dtype: Any) -> np.ndarray:
426
+ """Reconstruct array from ASAM COLUMN_DIR memory layout.
427
+
428
+ In COLUMN_DIR only the X and Y dimensions (last two in numpy convention)
429
+ are swapped compared to ROW_DIR. Higher dimensions (Z, Z4, Z5) keep
430
+ C-order. This is **not** true column-major (Fortran) order for dims > 2.
431
+
432
+ Args:
433
+ data: Raw byte buffer.
434
+ numpy_shape: Target shape in numpy convention (rows, cols, …).
435
+ dtype: NumPy dtype.
436
+
437
+ Returns:
438
+ NumPy array with the specified shape.
439
+ """
440
+ dt = np.dtype(dtype)
441
+ flat = np.frombuffer(data, dtype=dt)
442
+
443
+ if numpy_shape is None or len(numpy_shape) <= 1:
444
+ return flat.reshape(numpy_shape) if numpy_shape else flat
445
+
446
+ if len(numpy_shape) == 2:
447
+ return flat.reshape(numpy_shape, order="F")
448
+
449
+ # 3D+: only the last two dims (Y, X) are Fortran-ordered per slice.
450
+ higher_dims = numpy_shape[:-2]
451
+ yx_shape = numpy_shape[-2:]
452
+ num_slices = reduce(mul, higher_dims, 1)
453
+ slice_size = reduce(mul, yx_shape, 1)
454
+
455
+ sliced = flat.reshape(num_slices, slice_size)
456
+ out = np.empty((num_slices, *yx_shape), dtype=dt)
457
+ for idx in range(num_slices):
458
+ out[idx] = sliced[idx].reshape(yx_shape, order="F")
459
+ return out.reshape(numpy_shape)
460
+
461
+
462
+ def _asam_column_dir_to_buffer(arr: np.ndarray) -> bytearray:
463
+ """Serialize array for ASAM COLUMN_DIR memory layout.
464
+
465
+ Only the X and Y dimensions (last two in numpy convention) are
466
+ transposed. Higher dimensions keep C-order.
467
+
468
+ Args:
469
+ arr: NumPy array to serialize.
470
+
471
+ Returns:
472
+ Byte buffer with COLUMN_DIR memory layout.
473
+ """
474
+ if arr.ndim <= 1:
475
+ return bytearray(arr.tobytes())
476
+
477
+ if arr.ndim == 2:
478
+ return bytearray(arr.tobytes("F"))
479
+
480
+ shape = arr.shape
481
+ higher_dims = shape[:-2]
482
+ yx_shape = shape[-2:]
483
+ num_slices = reduce(mul, higher_dims, 1)
484
+ slice_bytes = reduce(mul, yx_shape, 1) * arr.dtype.itemsize
485
+ rs_arr = arr.reshape(num_slices, *yx_shape)
486
+ result = bytearray(arr.nbytes)
487
+ offset = 0
488
+ for idx in range(num_slices):
489
+ result[offset : offset + slice_bytes] = rs_arr[idx].tobytes("F")
490
+ offset += slice_bytes
491
+ return result
492
+
493
+
422
494
  @dataclass(repr=False, order=True)
423
495
  class Section:
424
496
  """Continuous block of bytes with start address and known length.
@@ -978,17 +1050,44 @@ class Section:
978
1050
  array: np.ndarray,
979
1051
  dtype: str,
980
1052
  byte_order: str = "MSB_LAST",
981
- order: str = None,
1053
+ index_mode: str = "ROW_DIR",
982
1054
  **kws,
983
1055
  ) -> None:
1056
+ """Write a NumPy ndarray using ASAM datatype and byte order semantics.
1057
+
1058
+ Unlike :meth:`write_ndarray`, this method uses ASAM conventions:
1059
+
1060
+ - ``index_mode`` controls the memory layout (``ROW_DIR`` or
1061
+ ``COLUMN_DIR``) instead of the generic ``order`` parameter.
1062
+ - Byte-order permutations (word-swap variants) are applied per
1063
+ element.
1064
+
1065
+ Args:
1066
+ addr: Absolute memory address to write to.
1067
+ array: NumPy array to write (shape in **numpy** convention,
1068
+ i.e. rows × columns).
1069
+ dtype: ASAM datatype name (e.g. ``"UWORD"``, ``"ULONG"``).
1070
+ byte_order: ASAM byte order string.
1071
+ index_mode: ``"ROW_DIR"`` (default) – X increments fastest
1072
+ (C-like row-major). ``"COLUMN_DIR"`` – Y increments
1073
+ fastest; only X and Y are swapped (not true column-major
1074
+ for dims > 2).
1075
+ **kws: Passed through to :meth:`write`.
1076
+
1077
+ Raises:
1078
+ TypeError: If *array* is not an ndarray.
1079
+ ValueError: If *index_mode* is unsupported.
1080
+ """
984
1081
  if not isinstance(array, np.ndarray):
985
1082
  raise TypeError("array must be of type numpy.ndarray.")
1083
+ if index_mode not in ASAM_INDEX_MODES:
1084
+ raise ValueError(f"Unsupported index_mode {index_mode!r}; use ROW_DIR or COLUMN_DIR.")
986
1085
 
987
1086
  asam_byte_order = self._resolve_asam_byteorder(byte_order)
988
1087
  internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
989
1088
  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)
1089
+ if index_mode == "COLUMN_DIR":
1090
+ raw_data = _asam_column_dir_to_buffer(typed_array)
992
1091
  else:
993
1092
  raw_data = typed_array.tobytes()
994
1093
  permuted = self._permute_asam_buffer(raw_data, internal_dtype, asam_byte_order)
@@ -1015,20 +1114,63 @@ class Section:
1015
1114
  length: int,
1016
1115
  dtype: str,
1017
1116
  shape: tuple = None,
1018
- order: str = None,
1019
1117
  byte_order: str = "MSB_LAST",
1118
+ index_mode: str = "ROW_DIR",
1020
1119
  **kws,
1021
1120
  ) -> np.ndarray:
1121
+ """Read a NumPy ndarray using ASAM datatype and byte order semantics.
1122
+
1123
+ Unlike :meth:`read_ndarray`, this method uses ASAM conventions:
1124
+
1125
+ - ``length`` is the **element count** (not byte count).
1126
+ - ``shape`` uses ASAM dimension order ``(X, Y, Z, Z4, Z5)``
1127
+ which is **reversed** compared to numpy ``(…, Z, Y, X)``.
1128
+ - ``index_mode`` controls the memory layout instead of the
1129
+ generic ``order`` parameter.
1130
+
1131
+ Args:
1132
+ addr: Absolute memory address to read from.
1133
+ length: Number of **elements** to read.
1134
+ dtype: ASAM datatype name (e.g. ``"UWORD"``, ``"ULONG"``).
1135
+ shape: Array dimensions in **ASAM** convention ``(X, Y, …)``.
1136
+ Converted internally to numpy convention ``(…, Y, X)``.
1137
+ byte_order: ASAM byte order string.
1138
+ index_mode: ``"ROW_DIR"`` (default) – X increments fastest
1139
+ (C-like row-major). ``"COLUMN_DIR"`` – Y increments
1140
+ fastest; only X and Y are swapped (not true column-major
1141
+ for dims > 2).
1142
+ **kws: Passed through to :meth:`read`.
1143
+
1144
+ Returns:
1145
+ NumPy array with shape in **numpy** convention.
1146
+
1147
+ Raises:
1148
+ ValueError: If *index_mode* is unsupported.
1149
+ """
1150
+ if index_mode not in ASAM_INDEX_MODES:
1151
+ raise ValueError(f"Unsupported index_mode {index_mode!r}; use ROW_DIR or COLUMN_DIR.")
1152
+
1022
1153
  asam_byte_order = self._resolve_asam_byteorder(byte_order)
1023
1154
  internal_dtype = self._asam_numeric_dtype_to_internal(dtype, asam_byte_order)
1024
1155
  dt = self._numpy_dtype_from_internal(internal_dtype)
1025
- raw_data = self.read(addr, length, **kws)
1156
+
1157
+ # Compute byte count from element count.
1158
+ type_name = internal_dtype.split("_")[0]
1159
+ type_size = TYPE_SIZES[type_name]
1160
+ byte_count = length * type_size
1161
+
1162
+ # Convert ASAM shape (X, Y, Z …) → numpy shape (… Z, Y, X).
1163
+ numpy_shape = tuple(reversed(shape)) if shape else None
1164
+
1165
+ raw_data = self.read(addr, byte_count, **kws)
1026
1166
  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
1167
 
1168
+ if index_mode == "COLUMN_DIR":
1169
+ return _asam_column_dir_from_buffer(permuted, numpy_shape, dt)
1170
+
1171
+ # ROW_DIR: standard C-order.
1030
1172
  flat = np.frombuffer(permuted, dtype=dt)
1031
- return flat.reshape(shape) if shape else flat
1173
+ return flat.reshape(numpy_shape) if numpy_shape else flat
1032
1174
 
1033
1175
  """
1034
1176
  def write_timestamp():
@@ -15,7 +15,6 @@ import pytest
15
15
  from objutils import dumps, loads
16
16
  from objutils.hexfile import InvalidRecordChecksumError, InvalidRecordLengthError
17
17
 
18
-
19
18
  # ---------------------------------------------------------------------------
20
19
  # Test vectors
21
20
  # ---------------------------------------------------------------------------
@@ -39,7 +38,7 @@ S19 = (
39
38
  )
40
39
 
41
40
  # Single-byte records for boundary / 24-bit address tests.
42
- ETEK_SINGLE_BYTE_LOW = b"%0C628000001AB\n" # address 0x000001, data 0xAB
41
+ ETEK_SINGLE_BYTE_LOW = b"%0C628000001AB\n" # address 0x000001, data 0xAB
43
42
  ETEK_SINGLE_BYTE_HIGH = b"%0C649FF0000CD\n" # address 0xFF0000, data 0xCD
44
43
 
45
44
 
@@ -72,13 +71,13 @@ def test_loads_produces_correct_srec_output():
72
71
  def test_loads_single_byte_low_address():
73
72
  """Single data byte at address 0x000001 is read correctly."""
74
73
  img = _etek_loads(ETEK_SINGLE_BYTE_LOW)
75
- assert img.read(0x000001, 1) == b"\xAB"
74
+ assert img.read(0x000001, 1) == b"\xab"
76
75
 
77
76
 
78
77
  def test_loads_single_byte_high_address():
79
78
  """Single data byte at address 0xFF0000 (max 24-bit) is read correctly."""
80
79
  img = _etek_loads(ETEK_SINGLE_BYTE_HIGH)
81
- assert img.read(0xFF0000, 1) == b"\xCD"
80
+ assert img.read(0xFF0000, 1) == b"\xcd"
82
81
 
83
82
 
84
83
  def test_loads_image_min_address():
@@ -158,4 +157,3 @@ def test_invalid_length_raises():
158
157
  bad = b"%2C6C200B000576F77212044696420796F7520726561\n"
159
158
  with pytest.raises(InvalidRecordLengthError):
160
159
  _etek_loads(bad)
161
-
@@ -1364,9 +1364,9 @@ def test_image_asam_numeric_array_word_swap_32bit():
1364
1364
  def test_image_asam_ndarray_fortran_roundtrip():
1365
1365
  img = Image(Section(data=bytearray(32), start_address=0x1000))
1366
1366
  arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint16)
1367
- img.write_asam_ndarray(0x1000, arr, "UWORD", byte_order="MSB_FIRST_MSW_LAST", order="F")
1367
+ img.write_asam_ndarray(0x1000, arr, "UWORD", byte_order="MSB_FIRST_MSW_LAST", index_mode="COLUMN_DIR")
1368
1368
  assert img.read(0x1000, 12) == b"\x01\x00\x04\x00\x02\x00\x05\x00\x03\x00\x06\x00"
1369
- result = img.read_asam_ndarray(0x1000, 12, "UWORD", shape=(3, 2), order="F", byte_order="MSB_FIRST_MSW_LAST")
1369
+ result = img.read_asam_ndarray(0x1000, 6, "UWORD", shape=(3, 2), index_mode="COLUMN_DIR", byte_order="MSB_FIRST_MSW_LAST")
1370
1370
  assert np.array_equal(result, arr)
1371
1371
 
1372
1372