dissect.cstruct 5.0.dev0__py3-none-any.whl → 5.0.dev7__py3-none-any.whl
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.
- dissect/cstruct/__init__.py +2 -2
- dissect/cstruct/bitbuffer.py +3 -3
- dissect/cstruct/compiler.py +7 -7
- dissect/cstruct/cstruct.py +192 -65
- dissect/cstruct/expression.py +8 -9
- dissect/cstruct/lexer.py +17 -7
- dissect/cstruct/parser.py +31 -13
- dissect/cstruct/tools/stubgen.py +18 -22
- dissect/cstruct/types/base.py +78 -33
- dissect/cstruct/types/char.py +21 -11
- dissect/cstruct/types/enum.py +33 -13
- dissect/cstruct/types/int.py +9 -7
- dissect/cstruct/types/leb128.py +6 -4
- dissect/cstruct/types/packed.py +19 -10
- dissect/cstruct/types/pointer.py +74 -56
- dissect/cstruct/types/structure.py +130 -30
- dissect/cstruct/types/void.py +6 -4
- dissect/cstruct/types/wchar.py +26 -14
- dissect/cstruct/{utils.py → util.py} +36 -25
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/METADATA +1 -1
- dissect_cstruct-5.0.dev7.dist-info/RECORD +32 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/scm_file_list.json +2 -2
- dissect_cstruct-5.0.dev7.dist-info/scm_version.json +8 -0
- dissect_cstruct-5.0.dev0.dist-info/RECORD +0 -32
- dissect_cstruct-5.0.dev0.dist-info/scm_version.json +0 -8
- /dissect/cstruct/{exceptions.py → exception.py} +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/WHEEL +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/entry_points.txt +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/COPYRIGHT +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/licenses/LICENSE +0 -0
- {dissect_cstruct-5.0.dev0.dist-info → dissect_cstruct-5.0.dev7.dist-info}/top_level.txt +0 -0
dissect/cstruct/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dissect.cstruct.bitbuffer import BitBuffer
|
|
4
4
|
from dissect.cstruct.cstruct import cstruct, ctypes, ctypes_type
|
|
5
|
-
from dissect.cstruct.
|
|
5
|
+
from dissect.cstruct.exception import (
|
|
6
6
|
Error,
|
|
7
7
|
NullPointerDereference,
|
|
8
8
|
ParserError,
|
|
@@ -28,7 +28,7 @@ from dissect.cstruct.types import (
|
|
|
28
28
|
Wchar,
|
|
29
29
|
WcharArray,
|
|
30
30
|
)
|
|
31
|
-
from dissect.cstruct.
|
|
31
|
+
from dissect.cstruct.util import (
|
|
32
32
|
dumpstruct,
|
|
33
33
|
hexdump,
|
|
34
34
|
p8,
|
dissect/cstruct/bitbuffer.py
CHANGED
|
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|
|
9
9
|
class BitBuffer:
|
|
10
10
|
"""Implements a bit buffer that can read and write bit fields."""
|
|
11
11
|
|
|
12
|
-
def __init__(self, stream: BinaryIO, endian: str):
|
|
12
|
+
def __init__(self, stream: BinaryIO, *, endian: str):
|
|
13
13
|
self.stream = stream
|
|
14
14
|
self.endian = endian
|
|
15
15
|
|
|
@@ -24,7 +24,7 @@ class BitBuffer:
|
|
|
24
24
|
|
|
25
25
|
self._type = field_type
|
|
26
26
|
self._remaining = field_type.size * 8
|
|
27
|
-
self._buffer = field_type._read(self.stream)
|
|
27
|
+
self._buffer = field_type._read(self.stream, endian=self.endian)
|
|
28
28
|
|
|
29
29
|
if isinstance(self._buffer, bytes):
|
|
30
30
|
if self.endian == "<":
|
|
@@ -71,7 +71,7 @@ class BitBuffer:
|
|
|
71
71
|
|
|
72
72
|
def flush(self) -> None:
|
|
73
73
|
if self._type is not None:
|
|
74
|
-
self._type._write(self.stream, self._buffer)
|
|
74
|
+
self._type._write(self.stream, self._buffer, endian=self.endian)
|
|
75
75
|
self._type = None
|
|
76
76
|
self._remaining = 0
|
|
77
77
|
self._buffer = 0
|
dissect/cstruct/compiler.py
CHANGED
|
@@ -119,7 +119,7 @@ class _ReadSourceGenerator:
|
|
|
119
119
|
"""
|
|
120
120
|
|
|
121
121
|
if any(field.bits for field in self.fields):
|
|
122
|
-
preamble += "bit_reader = BitBuffer(stream,
|
|
122
|
+
preamble += "bit_reader = BitBuffer(stream, endian=endian)\n"
|
|
123
123
|
|
|
124
124
|
read_code = "\n".join(self._generate_fields())
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ class _ReadSourceGenerator:
|
|
|
132
132
|
|
|
133
133
|
code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
|
|
134
134
|
|
|
135
|
-
return f"def _read(cls, stream, context=None):\n{code}"
|
|
135
|
+
return f"def _read(cls, stream, *, context=None, endian):\n{code}"
|
|
136
136
|
|
|
137
137
|
def _generate_fields(self) -> Iterator[str]:
|
|
138
138
|
current_offset = 0
|
|
@@ -229,7 +229,7 @@ class _ReadSourceGenerator:
|
|
|
229
229
|
def _generate_structure(self, field: Field) -> Iterator[str]:
|
|
230
230
|
template = f"""
|
|
231
231
|
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
232
|
-
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
232
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
|
|
233
233
|
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
234
234
|
"""
|
|
235
235
|
|
|
@@ -238,7 +238,7 @@ class _ReadSourceGenerator:
|
|
|
238
238
|
def _generate_array(self, field: Field) -> Iterator[str]:
|
|
239
239
|
template = f"""
|
|
240
240
|
{"_s = stream.tell()" if field.type.dynamic else ""}
|
|
241
|
-
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r)
|
|
241
|
+
r["{field._name}"] = {self._map_field(field)}._read(stream, context=r, endian=endian)
|
|
242
242
|
{f's["{field._name}"] = stream.tell() - _s' if field.type.dynamic else ""}
|
|
243
243
|
"""
|
|
244
244
|
|
|
@@ -311,7 +311,7 @@ class _ReadSourceGenerator:
|
|
|
311
311
|
item_parser = parser_template.format(type="_et", getter=f"_b[i:i + {field_type.type.size}]")
|
|
312
312
|
list_comp = f"[{item_parser} for i in range(0, {count}, {field_type.type.size})]"
|
|
313
313
|
elif issubclass(field_type.type, Pointer):
|
|
314
|
-
item_parser = "_et.__new__(_et, e, stream, r)"
|
|
314
|
+
item_parser = "_et.__new__(_et, e, stream, context=r, endian=endian)"
|
|
315
315
|
list_comp = f"[{item_parser} for e in {getter}]"
|
|
316
316
|
else:
|
|
317
317
|
item_parser = parser_template.format(type="_et", getter="e")
|
|
@@ -322,7 +322,7 @@ class _ReadSourceGenerator:
|
|
|
322
322
|
parser = f"type.__call__({self._map_field(field)}, {getter})"
|
|
323
323
|
elif issubclass(field_type, Pointer):
|
|
324
324
|
reads.append(f"_pt = {self._map_field(field)}")
|
|
325
|
-
parser = f"_pt.__new__(_pt, {getter}, stream, r)"
|
|
325
|
+
parser = f"_pt.__new__(_pt, {getter}, stream, context=r, endian=endian)"
|
|
326
326
|
else:
|
|
327
327
|
parser = parser_template.format(type=self._map_field(field), getter=getter)
|
|
328
328
|
|
|
@@ -335,7 +335,7 @@ class _ReadSourceGenerator:
|
|
|
335
335
|
if fmt == "x" or (len(fmt) == 2 and fmt[1] == "x"):
|
|
336
336
|
unpack = ""
|
|
337
337
|
else:
|
|
338
|
-
unpack = f'data = _struct(
|
|
338
|
+
unpack = f'data = _struct(endian, "{fmt}").unpack(buf)\n'
|
|
339
339
|
|
|
340
340
|
template = f"""
|
|
341
341
|
buf = stream.read({size})
|
dissect/cstruct/cstruct.py
CHANGED
|
@@ -2,13 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import ctypes as _ctypes
|
|
5
|
+
import inspect
|
|
5
6
|
import struct
|
|
6
7
|
import sys
|
|
7
8
|
import types
|
|
9
|
+
import warnings
|
|
10
|
+
from contextlib import contextmanager
|
|
8
11
|
from pathlib import Path
|
|
9
|
-
from typing import TYPE_CHECKING, Any, BinaryIO, TypeVar, cast
|
|
12
|
+
from typing import TYPE_CHECKING, Any, BinaryIO, Literal, TypeVar, cast
|
|
10
13
|
|
|
11
|
-
from dissect.cstruct.
|
|
14
|
+
from dissect.cstruct.exception import ResolveError
|
|
12
15
|
from dissect.cstruct.expression import Expression
|
|
13
16
|
from dissect.cstruct.parser import CStyleParser
|
|
14
17
|
from dissect.cstruct.types import (
|
|
@@ -26,10 +29,10 @@ from dissect.cstruct.types import (
|
|
|
26
29
|
Void,
|
|
27
30
|
Wchar,
|
|
28
31
|
)
|
|
29
|
-
from dissect.cstruct.types.base import
|
|
32
|
+
from dissect.cstruct.types.base import normalize_endianness
|
|
30
33
|
|
|
31
34
|
if TYPE_CHECKING:
|
|
32
|
-
from collections.abc import Iterable
|
|
35
|
+
from collections.abc import Iterable, Iterator
|
|
33
36
|
from typing import TypeAlias
|
|
34
37
|
|
|
35
38
|
from dissect.cstruct.types import (
|
|
@@ -40,25 +43,30 @@ if TYPE_CHECKING:
|
|
|
40
43
|
|
|
41
44
|
T = TypeVar("T", bound=BaseType)
|
|
42
45
|
|
|
46
|
+
AllowedEndianness: TypeAlias = Literal["little", "big", "network", "<", ">", "!", "@", "="]
|
|
47
|
+
Endianness: TypeAlias = Literal["<", ">", "!", "@", "="]
|
|
48
|
+
|
|
43
49
|
|
|
44
50
|
class cstruct:
|
|
45
51
|
"""Main class of cstruct. All types are registered in here.
|
|
46
52
|
|
|
47
53
|
Args:
|
|
48
|
-
endian: The endianness to use when parsing.
|
|
54
|
+
endian: The endianness to use when parsing (little, big, network, <, >, !, @ or =).
|
|
49
55
|
pointer: The pointer type to use for pointers.
|
|
50
56
|
"""
|
|
51
57
|
|
|
52
58
|
DEF_CSTYLE = 1
|
|
53
59
|
DEF_LEGACY = 2
|
|
54
60
|
|
|
55
|
-
def __init__(self, load: str = "", *, endian:
|
|
56
|
-
self.endian = endian
|
|
61
|
+
def __init__(self, load: str = "", *, endian: AllowedEndianness = "<", pointer: str | None = None):
|
|
62
|
+
self.endian = normalize_endianness(endian)
|
|
63
|
+
|
|
64
|
+
self.consts: dict[str, int | str | bytes] = {}
|
|
65
|
+
self.types: dict[str, type[BaseType]] = {}
|
|
66
|
+
self.includes: list[str] = []
|
|
57
67
|
|
|
58
|
-
self.consts = {}
|
|
59
|
-
self.includes = []
|
|
60
68
|
# fmt: off
|
|
61
|
-
|
|
69
|
+
initial_types = {
|
|
62
70
|
# Internal types
|
|
63
71
|
"int8": self._make_packed_type("int8", "b", int),
|
|
64
72
|
"uint8": self._make_packed_type("uint8", "B", int),
|
|
@@ -102,6 +110,22 @@ class cstruct:
|
|
|
102
110
|
"signed long long": "int64",
|
|
103
111
|
"unsigned long long": "uint64",
|
|
104
112
|
|
|
113
|
+
# Other convenience types
|
|
114
|
+
"u8": "uint8",
|
|
115
|
+
"u16": "uint16",
|
|
116
|
+
"u32": "uint32",
|
|
117
|
+
"u64": "uint64",
|
|
118
|
+
"u128": "uint128",
|
|
119
|
+
"__u8": "uint8",
|
|
120
|
+
"__u16": "uint16",
|
|
121
|
+
"__u32": "uint32",
|
|
122
|
+
"__u64": "uint64",
|
|
123
|
+
"__u128": "uint128",
|
|
124
|
+
"uchar": "uint8",
|
|
125
|
+
"ushort": "uint16",
|
|
126
|
+
"uint": "uint32",
|
|
127
|
+
"ulong": "uint32",
|
|
128
|
+
|
|
105
129
|
# Windows types
|
|
106
130
|
"BYTE": "uint8",
|
|
107
131
|
"CHAR": "char",
|
|
@@ -169,24 +193,12 @@ class cstruct:
|
|
|
169
193
|
"_DWORD": "uint32",
|
|
170
194
|
"_QWORD": "uint64",
|
|
171
195
|
"_OWORD": "uint128",
|
|
172
|
-
|
|
173
|
-
# Other convenience types
|
|
174
|
-
"u1": "uint8",
|
|
175
|
-
"u2": "uint16",
|
|
176
|
-
"u4": "uint32",
|
|
177
|
-
"u8": "uint64",
|
|
178
|
-
"u16": "uint128",
|
|
179
|
-
"__u8": "uint8",
|
|
180
|
-
"__u16": "uint16",
|
|
181
|
-
"__u32": "uint32",
|
|
182
|
-
"__u64": "uint64",
|
|
183
|
-
"uchar": "uint8",
|
|
184
|
-
"ushort": "uint16",
|
|
185
|
-
"uint": "uint32",
|
|
186
|
-
"ulong": "uint32",
|
|
187
196
|
}
|
|
188
197
|
# fmt: on
|
|
189
198
|
|
|
199
|
+
for name, type_ in initial_types.items():
|
|
200
|
+
self.add_type(name, type_)
|
|
201
|
+
|
|
190
202
|
pointer = pointer or ("uint64" if sys.maxsize > 2**32 else "uint32")
|
|
191
203
|
self.pointer: type[BaseType] = self.resolve(pointer)
|
|
192
204
|
self._anonymous_count = 0
|
|
@@ -201,27 +213,25 @@ class cstruct:
|
|
|
201
213
|
pass
|
|
202
214
|
|
|
203
215
|
try:
|
|
204
|
-
return self.
|
|
216
|
+
return self.types[attr]
|
|
205
217
|
except KeyError:
|
|
206
218
|
pass
|
|
207
219
|
|
|
208
|
-
raise AttributeError(f"
|
|
220
|
+
raise AttributeError(f"'{type(self).__name__}' object has no attribute {attr!r}")
|
|
209
221
|
|
|
210
222
|
def __copy__(self) -> cstruct:
|
|
211
223
|
cs = cstruct(endian=self.endian, pointer=self.pointer.__name__)
|
|
212
224
|
cs._anonymous_count = self._anonymous_count
|
|
213
|
-
cs.consts = self.consts.copy()
|
|
214
225
|
cs.includes = self.includes.copy()
|
|
215
226
|
|
|
216
|
-
# Update
|
|
217
|
-
for name,
|
|
218
|
-
|
|
219
|
-
|
|
227
|
+
# Update types to point to the new cstruct instance
|
|
228
|
+
for name, type_ in self.types.items():
|
|
229
|
+
new_type = copy.copy(type_)
|
|
230
|
+
new_type.cs = cs
|
|
231
|
+
cs.add_type(name, new_type, replace=True)
|
|
220
232
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
new_type.cs = cs
|
|
224
|
-
cs.typedefs[name] = new_type
|
|
233
|
+
for name, value in self.consts.items():
|
|
234
|
+
cs.add_const(name, value)
|
|
225
235
|
|
|
226
236
|
return cs
|
|
227
237
|
|
|
@@ -230,22 +240,34 @@ class cstruct:
|
|
|
230
240
|
self._anonymous_count += 1
|
|
231
241
|
return name
|
|
232
242
|
|
|
243
|
+
def _add_attr(self, name: str, value: Any) -> None:
|
|
244
|
+
# Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
|
|
245
|
+
# to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
|
|
246
|
+
if name in _RESERVED_NAMES:
|
|
247
|
+
return
|
|
248
|
+
setattr(self, name, value)
|
|
249
|
+
|
|
233
250
|
def add_type(self, name: str, type_: type[BaseType] | str, replace: bool = False) -> None:
|
|
234
|
-
"""Add a type or type
|
|
251
|
+
"""Add a type or type alias.
|
|
235
252
|
|
|
236
253
|
Only use this method when creating type aliases or adding already bound types.
|
|
254
|
+
All types will be resolved to their actual type objects prior to being added.
|
|
237
255
|
|
|
238
256
|
Args:
|
|
239
257
|
name: Name of the type to be added.
|
|
240
258
|
type_: The type to be added. Can be a str reference to another type or a compatible type class.
|
|
259
|
+
If a str is given, it will be resolved to the actual type object.
|
|
260
|
+
replace: Whether to replace the type if it already exists.
|
|
241
261
|
|
|
242
262
|
Raises:
|
|
243
263
|
ValueError: If the type already exists.
|
|
244
264
|
"""
|
|
245
|
-
|
|
265
|
+
typeobj = self.resolve(type_)
|
|
266
|
+
if not replace and (existing := self.types.get(name)) is not None and existing is not typeobj:
|
|
246
267
|
raise ValueError(f"Duplicate type: {name}")
|
|
247
268
|
|
|
248
|
-
self.
|
|
269
|
+
self.types[name] = typeobj
|
|
270
|
+
self._add_attr(name, typeobj)
|
|
249
271
|
|
|
250
272
|
addtype = add_type
|
|
251
273
|
|
|
@@ -264,8 +286,49 @@ class cstruct:
|
|
|
264
286
|
alignment: The alignment of the type.
|
|
265
287
|
**kwargs: Additional attributes to add to the type.
|
|
266
288
|
"""
|
|
289
|
+
# In cstruct 5.0 we changed the function signature of _read and _write
|
|
290
|
+
# Check if the function signature is compatible, and warn if not
|
|
291
|
+
for type_to_check in (type_, type_.ArrayType):
|
|
292
|
+
type_name = type_.__name__ + (f".{type_.ArrayType.__name__}" if type_to_check is type_.ArrayType else "")
|
|
293
|
+
|
|
294
|
+
for method in ("_read", "_read_array", "_read_0", "_write", "_write_array", "_write_0"):
|
|
295
|
+
if not hasattr(type_to_check, method):
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
signature = inspect.signature(getattr(type_to_check, method))
|
|
299
|
+
|
|
300
|
+
# Only warn if the method doesn't accept an endian parameter
|
|
301
|
+
if "endian" not in signature.parameters:
|
|
302
|
+
warnings.warn(
|
|
303
|
+
f"Custom type {type_name} is missing the 'endian' keyword-only parameter in its {method} method. " # noqa: E501
|
|
304
|
+
"Please refer to the changelog of dissect.cstruct 5.0 for more information.",
|
|
305
|
+
stacklevel=2,
|
|
306
|
+
)
|
|
307
|
+
|
|
267
308
|
self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
|
|
268
309
|
|
|
310
|
+
def add_const(self, name: str, value: Any) -> None:
|
|
311
|
+
"""Add a constant value.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
name: Name of the constant to be added.
|
|
315
|
+
value: The value of the constant.
|
|
316
|
+
"""
|
|
317
|
+
self.consts[name] = value
|
|
318
|
+
self._add_attr(name, value)
|
|
319
|
+
|
|
320
|
+
def del_const(self, name: str) -> None:
|
|
321
|
+
"""Delete a constant value.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
name: Name of the constant to be deleted.
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
KeyError: If the constant does not exist.
|
|
328
|
+
"""
|
|
329
|
+
del self.consts[name]
|
|
330
|
+
self.__dict__.pop(name, None)
|
|
331
|
+
|
|
269
332
|
def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
|
|
270
333
|
"""Parse structures from the given definitions using the given definition type.
|
|
271
334
|
|
|
@@ -301,6 +364,42 @@ class cstruct:
|
|
|
301
364
|
with Path(path).open() as fh:
|
|
302
365
|
self.load(fh.read(), deftype, **kwargs)
|
|
303
366
|
|
|
367
|
+
def cdef(self) -> str:
|
|
368
|
+
"""Render all constants, structure, union, enum and flag definitions back to their C-style definitions.
|
|
369
|
+
|
|
370
|
+
Note that the result is semantically equivalent to the original definitions, but not necessarily identical.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
The C-style definitions as a string.
|
|
374
|
+
"""
|
|
375
|
+
empty = cstruct()
|
|
376
|
+
|
|
377
|
+
blocks = []
|
|
378
|
+
|
|
379
|
+
defines = []
|
|
380
|
+
for name, value in self.consts.items():
|
|
381
|
+
if name in empty.consts:
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
defines.append(f"#define {name} {value!r}")
|
|
385
|
+
|
|
386
|
+
if defines:
|
|
387
|
+
blocks.append("\n".join(defines))
|
|
388
|
+
|
|
389
|
+
for name, typedef in self.types.items():
|
|
390
|
+
if name in empty.types or not isinstance(typedef, type):
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
if not issubclass(typedef, (Structure, Enum, Flag)):
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
if typedef.__name__ == name:
|
|
397
|
+
blocks.append(typedef.cdef())
|
|
398
|
+
else:
|
|
399
|
+
blocks.append(f"typedef {typedef.__name__} {name};")
|
|
400
|
+
|
|
401
|
+
return "\n\n".join(blocks)
|
|
402
|
+
|
|
304
403
|
def read(self, name: str, stream: BinaryIO) -> Any:
|
|
305
404
|
"""Parse data using a given type.
|
|
306
405
|
|
|
@@ -328,20 +427,13 @@ class cstruct:
|
|
|
328
427
|
Raises:
|
|
329
428
|
ResolveError: If the type can't be resolved.
|
|
330
429
|
"""
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return type_name
|
|
430
|
+
if not isinstance(name, str):
|
|
431
|
+
return name
|
|
334
432
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
type_name = self.typedefs[type_name]
|
|
340
|
-
|
|
341
|
-
if not isinstance(type_name, str):
|
|
342
|
-
return type_name
|
|
343
|
-
|
|
344
|
-
raise ResolveError(f"Recursion limit exceeded while resolving type {name}")
|
|
433
|
+
try:
|
|
434
|
+
return self.types[name]
|
|
435
|
+
except KeyError:
|
|
436
|
+
raise ResolveError(f"Unknown type {name}") from None
|
|
345
437
|
|
|
346
438
|
def copy(self) -> cstruct:
|
|
347
439
|
"""Create a copy of this cstruct instance.
|
|
@@ -351,6 +443,28 @@ class cstruct:
|
|
|
351
443
|
"""
|
|
352
444
|
return copy.copy(self)
|
|
353
445
|
|
|
446
|
+
@contextmanager
|
|
447
|
+
def endianness(self, endian: AllowedEndianness) -> Iterator[cstruct]:
|
|
448
|
+
"""Context manager for temporarily changing the endianness of this cstruct instance."""
|
|
449
|
+
original = self.endian
|
|
450
|
+
self.endian = normalize_endianness(endian)
|
|
451
|
+
try:
|
|
452
|
+
yield self
|
|
453
|
+
finally:
|
|
454
|
+
self.endian = original
|
|
455
|
+
|
|
456
|
+
@contextmanager
|
|
457
|
+
def big_endian(self) -> Iterator[cstruct]:
|
|
458
|
+
"""Context manager for temporarily changing the endianness to big endian."""
|
|
459
|
+
with self.endianness(">") as cs:
|
|
460
|
+
yield cs
|
|
461
|
+
|
|
462
|
+
@contextmanager
|
|
463
|
+
def little_endian(self) -> Iterator[cstruct]:
|
|
464
|
+
"""Context manager for temporarily changing the endianness to little endian."""
|
|
465
|
+
with self.endianness("<") as cs:
|
|
466
|
+
yield cs
|
|
467
|
+
|
|
354
468
|
def _make_type(
|
|
355
469
|
self,
|
|
356
470
|
name: str,
|
|
@@ -518,6 +632,21 @@ class cstruct:
|
|
|
518
632
|
# signed long long: TypeAlias = int64
|
|
519
633
|
# unsigned long long: TypeAlias = uint64
|
|
520
634
|
|
|
635
|
+
u8: TypeAlias = uint8
|
|
636
|
+
u16: TypeAlias = uint16
|
|
637
|
+
u32: TypeAlias = uint32
|
|
638
|
+
u64: TypeAlias = uint64
|
|
639
|
+
u128: TypeAlias = uint128
|
|
640
|
+
__u8: TypeAlias = uint8
|
|
641
|
+
__u16: TypeAlias = uint16
|
|
642
|
+
__u32: TypeAlias = uint32
|
|
643
|
+
__u64: TypeAlias = uint64
|
|
644
|
+
__u128: TypeAlias = uint128
|
|
645
|
+
uchar: TypeAlias = uint8
|
|
646
|
+
ushort: TypeAlias = uint16
|
|
647
|
+
uint: TypeAlias = uint32
|
|
648
|
+
ulong: TypeAlias = uint32
|
|
649
|
+
|
|
521
650
|
BYTE: TypeAlias = uint8
|
|
522
651
|
CHAR: TypeAlias = char
|
|
523
652
|
SHORT: TypeAlias = int16
|
|
@@ -583,19 +712,17 @@ class cstruct:
|
|
|
583
712
|
_QWORD: TypeAlias = uint64
|
|
584
713
|
_OWORD: TypeAlias = uint128
|
|
585
714
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
uint: TypeAlias = uint32
|
|
598
|
-
ulong: TypeAlias = uint32
|
|
715
|
+
|
|
716
|
+
# Attribute names that types and constants may never shadow: the public API and methods of the cstruct
|
|
717
|
+
# class itself, plus the instance attributes created in __init__.
|
|
718
|
+
_RESERVED_NAMES = frozenset(dir(cstruct)) | {
|
|
719
|
+
"endian",
|
|
720
|
+
"consts",
|
|
721
|
+
"types",
|
|
722
|
+
"includes",
|
|
723
|
+
"pointer",
|
|
724
|
+
"_anonymous_count",
|
|
725
|
+
}
|
|
599
726
|
|
|
600
727
|
|
|
601
728
|
def ctypes(structure: type[Structure]) -> type[_ctypes.Structure]:
|
dissect/cstruct/expression.py
CHANGED
|
@@ -2,9 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from dissect.cstruct.
|
|
5
|
+
from dissect.cstruct.exception import ExpressionParserError
|
|
6
6
|
from dissect.cstruct.lexer import IDENTIFIER_TYPES, Lexer, TokenCursor, TokenType
|
|
7
|
-
from dissect.cstruct.
|
|
7
|
+
from dissect.cstruct.util import offsetof, sizeof
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from collections.abc import Callable
|
|
@@ -12,6 +12,8 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from dissect.cstruct import cstruct
|
|
13
13
|
from dissect.cstruct.lexer import Token
|
|
14
14
|
|
|
15
|
+
_MISSING = object()
|
|
16
|
+
|
|
15
17
|
BINARY_OPERATORS: dict[TokenType, Callable[[int, int], int]] = {
|
|
16
18
|
TokenType.PIPE: lambda a, b: a | b,
|
|
17
19
|
TokenType.CARET: lambda a, b: a ^ b,
|
|
@@ -164,16 +166,13 @@ class Expression(TokenCursor):
|
|
|
164
166
|
obj = context[part]
|
|
165
167
|
elif part in cs.consts:
|
|
166
168
|
obj = cs.consts[part]
|
|
167
|
-
elif part in cs.
|
|
168
|
-
obj = cs.
|
|
169
|
+
elif part in cs.types:
|
|
170
|
+
obj = cs.types[part]
|
|
169
171
|
else:
|
|
170
172
|
raise self._error(f"Unknown identifier: '{ident}'", token=token)
|
|
171
173
|
else:
|
|
172
|
-
if isinstance(obj, dict)
|
|
173
|
-
|
|
174
|
-
elif hasattr(obj, part):
|
|
175
|
-
obj = getattr(obj, part)
|
|
176
|
-
else:
|
|
174
|
+
obj = obj.get(part, _MISSING) if isinstance(obj, dict) else getattr(obj, part, _MISSING)
|
|
175
|
+
if obj is _MISSING:
|
|
177
176
|
raise self._error(f"Unknown identifier: '{ident}'", token=token)
|
|
178
177
|
|
|
179
178
|
try:
|
dissect/cstruct/lexer.py
CHANGED
|
@@ -4,7 +4,7 @@ import enum
|
|
|
4
4
|
import re
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from dissect.cstruct.
|
|
7
|
+
from dissect.cstruct.exception import LexerError
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from collections.abc import Callable
|
|
@@ -134,6 +134,14 @@ def tokenize(data: str) -> list[Token]:
|
|
|
134
134
|
return Lexer(data).tokenize()
|
|
135
135
|
|
|
136
136
|
|
|
137
|
+
def format_error_context(data: str, lineno: int, window: int = 1) -> str:
|
|
138
|
+
"""Format a snippet of the input data around a given line number for error messages."""
|
|
139
|
+
start = max(0, lineno - window - 2)
|
|
140
|
+
context = data.splitlines()[start:lineno]
|
|
141
|
+
no_len = len(str(lineno)) + 2
|
|
142
|
+
return "\n".join(f"{start + i + 1:>{no_len}}: {line}" for i, line in enumerate(context))
|
|
143
|
+
|
|
144
|
+
|
|
137
145
|
class Lexer:
|
|
138
146
|
"""Lexer compatible with C-like syntax for struct definitions and preprocessor directives."""
|
|
139
147
|
|
|
@@ -213,8 +221,10 @@ class Lexer:
|
|
|
213
221
|
|
|
214
222
|
return self._take()
|
|
215
223
|
|
|
216
|
-
def _error(self, msg: str, *,
|
|
217
|
-
|
|
224
|
+
def _error(self, msg: str, *, lineno: int | None = None) -> LexerError:
|
|
225
|
+
lineno = lineno if lineno is not None else self._line
|
|
226
|
+
content = format_error_context(self.data, lineno)
|
|
227
|
+
return LexerError(f"line {lineno}: {msg}\n{content}")
|
|
218
228
|
|
|
219
229
|
def _emit(self, type: TokenType, value: str, line: int, column: int = 0) -> None:
|
|
220
230
|
"""Emit a token with the given type and value at the specified line and column."""
|
|
@@ -402,7 +412,7 @@ class Lexer:
|
|
|
402
412
|
keyword = self._read_identifier()
|
|
403
413
|
|
|
404
414
|
if (token_type := _PP_KEYWORDS.get(keyword)) is None:
|
|
405
|
-
raise self._error(f"unknown preprocessor directive '#{keyword}'",
|
|
415
|
+
raise self._error(f"unknown preprocessor directive '#{keyword}'", lineno=line)
|
|
406
416
|
|
|
407
417
|
self._emit(token_type, keyword, line, col)
|
|
408
418
|
|
|
@@ -410,7 +420,7 @@ class Lexer:
|
|
|
410
420
|
self._skip_whitespace_and_comments()
|
|
411
421
|
|
|
412
422
|
if not (name := self._read_identifier()):
|
|
413
|
-
raise self._error("expected identifier after '#define'",
|
|
423
|
+
raise self._error("expected identifier after '#define'", lineno=line)
|
|
414
424
|
self._emit(TokenType.IDENTIFIER, name, line)
|
|
415
425
|
|
|
416
426
|
self._skip_whitespace_and_comments()
|
|
@@ -434,7 +444,7 @@ class Lexer:
|
|
|
434
444
|
self._expect(">") # Consume closing `>`
|
|
435
445
|
value = f"<{value}>"
|
|
436
446
|
else:
|
|
437
|
-
raise self._error("expected include path after '#include'",
|
|
447
|
+
raise self._error("expected include path after '#include'", lineno=line)
|
|
438
448
|
|
|
439
449
|
self._emit(TokenType.STRING, value, line)
|
|
440
450
|
|
|
@@ -486,7 +496,7 @@ class Lexer:
|
|
|
486
496
|
self._emit(_SINGLE_CHARS[ch], self._take(), line, col)
|
|
487
497
|
|
|
488
498
|
else:
|
|
489
|
-
raise self._error(f"unexpected character {ch!r}",
|
|
499
|
+
raise self._error(f"unexpected character {ch!r}", lineno=line)
|
|
490
500
|
|
|
491
501
|
self._emit(TokenType.EOF, "", self._line, self._column)
|
|
492
502
|
return self._tokens
|