objutils 0.10.3__tar.gz → 0.10.5__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 (108) hide show
  1. {objutils-0.10.3 → objutils-0.10.5}/PKG-INFO +6 -2
  2. {objutils-0.10.3 → objutils-0.10.5}/docs/README.rst +5 -1
  3. {objutils-0.10.3 → objutils-0.10.5}/objutils/__init__.py +1 -1
  4. {objutils-0.10.3 → objutils-0.10.5}/objutils/cosmac.py +4 -7
  5. {objutils-0.10.3 → objutils-0.10.5}/objutils/elf/model.py +7 -2
  6. {objutils-0.10.3 → objutils-0.10.5}/objutils/emon52.py +1 -3
  7. {objutils-0.10.3 → objutils-0.10.5}/objutils/etek.py +4 -10
  8. {objutils-0.10.3 → objutils-0.10.5}/objutils/hexfile.py +1 -1
  9. {objutils-0.10.3 → objutils-0.10.5}/objutils/ihex.py +1 -2
  10. {objutils-0.10.3 → objutils-0.10.5}/objutils/image.py +30 -6
  11. {objutils-0.10.3 → objutils-0.10.5}/objutils/pecoff/__init__.py +12 -12
  12. {objutils-0.10.3 → objutils-0.10.5}/objutils/pecoff/model.py +4 -1
  13. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_coff_extract.py +26 -2
  14. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_coff_syms.py +2 -2
  15. objutils-0.10.5/objutils/scripts/oj_dwarf_info.py +143 -0
  16. {objutils-0.10.3 → objutils-0.10.5}/objutils/section.py +150 -8
  17. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_etek.py +3 -5
  18. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_image.py +2 -2
  19. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_section.py +339 -169
  20. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_section_join.py +0 -1
  21. {objutils-0.10.3 → objutils-0.10.5}/objutils/version.py +1 -1
  22. {objutils-0.10.3 → objutils-0.10.5}/pyproject.toml +2 -2
  23. {objutils-0.10.3 → objutils-0.10.5}/CMakeLists.txt +0 -0
  24. {objutils-0.10.3 → objutils-0.10.5}/LICENSE +0 -0
  25. {objutils-0.10.3 → objutils-0.10.5}/build_ext.py +0 -0
  26. {objutils-0.10.3 → objutils-0.10.5}/objutils/.coveragerc +0 -0
  27. {objutils-0.10.3 → objutils-0.10.5}/objutils/.vscode/launch.json +0 -0
  28. {objutils-0.10.3 → objutils-0.10.5}/objutils/.vscode/settings.json +0 -0
  29. {objutils-0.10.3 → objutils-0.10.5}/objutils/a2l_test.shf +0 -0
  30. {objutils-0.10.3 → objutils-0.10.5}/objutils/ash.py +0 -0
  31. {objutils-0.10.3 → objutils-0.10.5}/objutils/binfile.py +0 -0
  32. {objutils-0.10.3 → objutils-0.10.5}/objutils/checksums.py +0 -0
  33. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/.traverser.py.un~ +0 -0
  34. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/__init__.py +0 -0
  35. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/attrparser.py +0 -0
  36. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/c_generator.py +0 -0
  37. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/constants.py +0 -0
  38. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/encoding.py +0 -0
  39. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/lineprog.py +0 -0
  40. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/readers.py +0 -0
  41. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/sm.py +0 -0
  42. {objutils-0.10.3 → objutils-0.10.5}/objutils/dwarf/traverser.py +0 -0
  43. {objutils-0.10.3 → objutils-0.10.5}/objutils/elf/__init__.py +0 -0
  44. {objutils-0.10.3 → objutils-0.10.5}/objutils/elf/arm/__init__.py +0 -0
  45. {objutils-0.10.3 → objutils-0.10.5}/objutils/elf/arm/attributes.py +0 -0
  46. {objutils-0.10.3 → objutils-0.10.5}/objutils/elf/defs.py +0 -0
  47. {objutils-0.10.3 → objutils-0.10.5}/objutils/exceptions.py +0 -0
  48. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/__init__.py +0 -0
  49. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/ctre.hpp +0 -0
  50. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/difflib.h +0 -0
  51. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/exceptions.cpp +0 -0
  52. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/exceptions.hpp +0 -0
  53. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/hexfile.cpp +0 -0
  54. {objutils-0.10.3 → objutils-0.10.5}/objutils/extensions/wrapper.cpp +0 -0
  55. {objutils-0.10.3 → objutils-0.10.5}/objutils/fpc.py +0 -0
  56. {objutils-0.10.3 → objutils-0.10.5}/objutils/hexdump.py +0 -0
  57. {objutils-0.10.3 → objutils-0.10.5}/objutils/ieee695.py +0 -0
  58. {objutils-0.10.3 → objutils-0.10.5}/objutils/logger.py +0 -0
  59. {objutils-0.10.3 → objutils-0.10.5}/objutils/mostec.py +0 -0
  60. {objutils-0.10.3 → objutils-0.10.5}/objutils/objutils.code-workspace +0 -0
  61. {objutils-0.10.3 → objutils-0.10.5}/objutils/pecoff/defs.py +0 -0
  62. {objutils-0.10.3 → objutils-0.10.5}/objutils/pecoff/pdb/__init__.py +0 -0
  63. {objutils-0.10.3 → objutils-0.10.5}/objutils/pickleif.py +0 -0
  64. {objutils-0.10.3 → objutils-0.10.5}/objutils/rca.py +0 -0
  65. {objutils-0.10.3 → objutils-0.10.5}/objutils/readers.py +0 -0
  66. {objutils-0.10.3 → objutils-0.10.5}/objutils/registry.py +0 -0
  67. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/arduino_build_artifacts.py +0 -0
  68. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_cgen.py +0 -0
  69. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_coff_import.py +0 -0
  70. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_coff_info.py +0 -0
  71. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_dwarf_import.py +0 -0
  72. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_elf_arm_attrs.py +0 -0
  73. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_elf_extract.py +0 -0
  74. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_elf_import.py +0 -0
  75. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_elf_info.py +0 -0
  76. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_elf_syms.py +0 -0
  77. {objutils-0.10.3 → objutils-0.10.5}/objutils/scripts/oj_hex_info.py +0 -0
  78. {objutils-0.10.3 → objutils-0.10.5}/objutils/shf.py +0 -0
  79. {objutils-0.10.3 → objutils-0.10.5}/objutils/sig.py +0 -0
  80. {objutils-0.10.3 → objutils-0.10.5}/objutils/srec.py +0 -0
  81. {objutils-0.10.3 → objutils-0.10.5}/objutils/tek.py +0 -0
  82. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/__init__.py +0 -0
  83. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_arm_attributes.py +0 -0
  84. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_ash.py +0 -0
  85. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_c_generator.py +0 -0
  86. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_checksums.py +0 -0
  87. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_cygpath.py +0 -0
  88. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_diff_bin.py +0 -0
  89. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_elf.py +0 -0
  90. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_emon52.py +0 -0
  91. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_examples_cgen.py +0 -0
  92. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_fpc.py +0 -0
  93. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_hexdump.py +0 -0
  94. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_hexfile.py +0 -0
  95. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_ihex.py +0 -0
  96. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_mostec.py +0 -0
  97. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_readers.py +0 -0
  98. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_registry.py +0 -0
  99. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_repr.py +0 -0
  100. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_shf.py +0 -0
  101. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_sm.py +0 -0
  102. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_srec.py +0 -0
  103. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_tek.py +0 -0
  104. {objutils-0.10.3 → objutils-0.10.5}/objutils/tests/test_titext.py +0 -0
  105. {objutils-0.10.3 → objutils-0.10.5}/objutils/titxt.py +0 -0
  106. {objutils-0.10.3 → objutils-0.10.5}/objutils/utils/__init__.py +0 -0
  107. {objutils-0.10.3 → objutils-0.10.5}/objutils/utils/arduino.py +0 -0
  108. {objutils-0.10.3 → objutils-0.10.5}/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.5
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.5"
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
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python
2
+ """DWARF database inspector (read-only).
3
+
4
+ Lists compilation units or traverses DIE attributes from an existing ``.prgdb``
5
+ database. This script intentionally does **not** perform DWARF import; use
6
+ ``oj-dwarf-import`` to build the database first.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ from collections.abc import Iterable
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from sqlalchemy.exc import SQLAlchemyError
17
+
18
+ from objutils.dwarf.constants import Tag
19
+ from objutils.dwarf.traverser import AttributeParser
20
+ from objutils.elf import model
21
+
22
+
23
+ def _parse_offset(text: str) -> int:
24
+ try:
25
+ return int(text, 0)
26
+ except ValueError as exc:
27
+ raise argparse.ArgumentTypeError(f"Invalid offset '{text}'") from exc
28
+
29
+
30
+ def _attr_value(die: Any, name: str) -> Any:
31
+ attrs = getattr(die, "attributes_map", {}) or {}
32
+ attr = attrs.get(name)
33
+ return None if attr is None else getattr(attr, "raw_value", None)
34
+
35
+
36
+ def _print_cus(cus: Iterable[Any], *, quiet: bool) -> None:
37
+ if quiet:
38
+ return
39
+ print("Compilation Units:")
40
+ print("-------------------")
41
+ for idx, cu in enumerate(cus, 1):
42
+ name = _attr_value(cu, "name") or ""
43
+ low_pc = _attr_value(cu, "low_pc")
44
+ high_pc = _attr_value(cu, "high_pc")
45
+ low_pc_str = f"0x{int(low_pc):08x}" if low_pc is not None else "-"
46
+ high_pc_str = f"0x{int(high_pc):08x}" if high_pc is not None else "-"
47
+ print(f"[{idx:03d}] off=0x{int(cu.offset):08x} name={name} range={low_pc_str}..{high_pc_str}")
48
+
49
+
50
+ def _print_summary(session, *, quiet: bool) -> None:
51
+ if quiet:
52
+ return
53
+ die_count = session.query(model.DebugInformationEntry).count()
54
+ attr_count = session.query(model.DIEAttribute).count()
55
+ print(f"DIEs: {die_count:,}")
56
+ print(f"Attributes: {attr_count:,}")
57
+
58
+
59
+ def main(argv: list[str] | None = None) -> int:
60
+ parser = argparse.ArgumentParser(description="Inspect DWARF debug info stored in a .prgdb database (no import).")
61
+ parser.add_argument("db", help="Path to .prgdb database generated by oj-dwarf-import")
62
+ parser.add_argument("--quiet", "-q", action="store_true", help="Suppress non-error output")
63
+ parser.add_argument("--list-cus", action="store_true", help="List compile units")
64
+ parser.add_argument("--summary", action="store_true", help="Print DIE/attribute counts")
65
+ parser.add_argument(
66
+ "--walk-attrs",
67
+ action="store_true",
68
+ help="Traverse DIE attributes with AttributeParser (starts at first DIE unless --offset is set).",
69
+ )
70
+ parser.add_argument(
71
+ "--offset",
72
+ type=_parse_offset,
73
+ help="Absolute DIE offset (decimal or 0x-prefixed hex) used for --walk-attrs start point.",
74
+ )
75
+ args = parser.parse_args(argv)
76
+
77
+ db_path = Path(args.db)
78
+ if db_path.suffix.lower() != model.DB_EXTENSION:
79
+ parser.error(f"Expected a '{model.DB_EXTENSION}' database; got '{db_path}'")
80
+ if not db_path.exists():
81
+ parser.error(f"Database not found: {db_path}")
82
+
83
+ post_actions = args.list_cus or args.summary or args.walk_attrs
84
+ if not post_actions:
85
+ args.list_cus = True
86
+
87
+ try:
88
+ db = model.Model(str(db_path))
89
+ except (OSError, SQLAlchemyError, ValueError) as exc:
90
+ if not args.quiet:
91
+ print(f"Failed to open database '{db_path}': {exc}")
92
+ return 4
93
+
94
+ try:
95
+ session = db.session
96
+ if args.summary:
97
+ _print_summary(session, quiet=args.quiet)
98
+
99
+ if args.list_cus or args.walk_attrs:
100
+ DIE = model.DebugInformationEntry
101
+ raw_cus = session.query(DIE).filter(DIE.tag == Tag.compile_unit).order_by(DIE.offset, DIE.cu_start).all()
102
+ seen = set()
103
+ cus = []
104
+ for cu in raw_cus:
105
+ key = (cu.offset, cu.cu_start)
106
+ if key in seen:
107
+ continue
108
+ seen.add(key)
109
+ cus.append(cu)
110
+ if args.list_cus:
111
+ _print_cus(cus, quiet=args.quiet)
112
+
113
+ if args.walk_attrs:
114
+ ap = AttributeParser(session)
115
+ if args.offset is not None:
116
+ start_die = session.query(DIE).filter(DIE.offset == int(args.offset)).first()
117
+ if start_die is None:
118
+ if not args.quiet:
119
+ print(f"No DIE found at offset 0x{int(args.offset):08x}.")
120
+ return 5
121
+ ap.traverse_tree(start_die)
122
+ else:
123
+ if not cus:
124
+ if not args.quiet:
125
+ print("No DebugInformationEntry available in the database to traverse.")
126
+ return 5
127
+ for cu in cus:
128
+ ap.traverse_tree(cu)
129
+ except (OSError, SQLAlchemyError, RuntimeError, ValueError) as exc:
130
+ if not args.quiet:
131
+ print(f"Traversal failed: {exc}")
132
+ return 5
133
+ finally:
134
+ try:
135
+ db.close()
136
+ except SQLAlchemyError:
137
+ pass
138
+
139
+ return 0
140
+
141
+
142
+ if __name__ == "__main__":
143
+ raise SystemExit(main())