smallworld-re 1.0.0__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.
- smallworld/__init__.py +35 -0
- smallworld/analyses/__init__.py +14 -0
- smallworld/analyses/analysis.py +88 -0
- smallworld/analyses/code_coverage.py +31 -0
- smallworld/analyses/colorizer.py +682 -0
- smallworld/analyses/colorizer_summary.py +100 -0
- smallworld/analyses/field_detection/__init__.py +14 -0
- smallworld/analyses/field_detection/field_analysis.py +536 -0
- smallworld/analyses/field_detection/guards.py +26 -0
- smallworld/analyses/field_detection/hints.py +133 -0
- smallworld/analyses/field_detection/malloc.py +211 -0
- smallworld/analyses/forced_exec/__init__.py +3 -0
- smallworld/analyses/forced_exec/forced_exec.py +87 -0
- smallworld/analyses/underlays/__init__.py +4 -0
- smallworld/analyses/underlays/basic.py +13 -0
- smallworld/analyses/underlays/underlay.py +31 -0
- smallworld/analyses/unstable/__init__.py +4 -0
- smallworld/analyses/unstable/angr/__init__.py +0 -0
- smallworld/analyses/unstable/angr/base.py +12 -0
- smallworld/analyses/unstable/angr/divergence.py +274 -0
- smallworld/analyses/unstable/angr/model.py +383 -0
- smallworld/analyses/unstable/angr/nwbt.py +63 -0
- smallworld/analyses/unstable/angr/typedefs.py +170 -0
- smallworld/analyses/unstable/angr/utils.py +25 -0
- smallworld/analyses/unstable/angr/visitor.py +315 -0
- smallworld/analyses/unstable/angr_nwbt.py +106 -0
- smallworld/analyses/unstable/code_coverage.py +54 -0
- smallworld/analyses/unstable/code_reachable.py +44 -0
- smallworld/analyses/unstable/control_flow_tracer.py +71 -0
- smallworld/analyses/unstable/pointer_finder.py +90 -0
- smallworld/arch/__init__.py +0 -0
- smallworld/arch/aarch64_arch.py +286 -0
- smallworld/arch/amd64_arch.py +86 -0
- smallworld/arch/i386_arch.py +44 -0
- smallworld/emulators/__init__.py +14 -0
- smallworld/emulators/angr/__init__.py +7 -0
- smallworld/emulators/angr/angr.py +1652 -0
- smallworld/emulators/angr/default.py +15 -0
- smallworld/emulators/angr/exceptions.py +7 -0
- smallworld/emulators/angr/exploration/__init__.py +9 -0
- smallworld/emulators/angr/exploration/bounds.py +27 -0
- smallworld/emulators/angr/exploration/default.py +17 -0
- smallworld/emulators/angr/exploration/terminate.py +22 -0
- smallworld/emulators/angr/factory.py +55 -0
- smallworld/emulators/angr/machdefs/__init__.py +35 -0
- smallworld/emulators/angr/machdefs/aarch64.py +292 -0
- smallworld/emulators/angr/machdefs/amd64.py +192 -0
- smallworld/emulators/angr/machdefs/arm.py +387 -0
- smallworld/emulators/angr/machdefs/i386.py +221 -0
- smallworld/emulators/angr/machdefs/machdef.py +138 -0
- smallworld/emulators/angr/machdefs/mips.py +184 -0
- smallworld/emulators/angr/machdefs/mips64.py +189 -0
- smallworld/emulators/angr/machdefs/ppc.py +101 -0
- smallworld/emulators/angr/machdefs/riscv.py +261 -0
- smallworld/emulators/angr/machdefs/xtensa.py +255 -0
- smallworld/emulators/angr/memory/__init__.py +7 -0
- smallworld/emulators/angr/memory/default.py +10 -0
- smallworld/emulators/angr/memory/fixups.py +43 -0
- smallworld/emulators/angr/memory/memtrack.py +105 -0
- smallworld/emulators/angr/scratch.py +43 -0
- smallworld/emulators/angr/simos.py +53 -0
- smallworld/emulators/angr/utils.py +70 -0
- smallworld/emulators/emulator.py +1013 -0
- smallworld/emulators/hookable.py +252 -0
- smallworld/emulators/panda/__init__.py +5 -0
- smallworld/emulators/panda/machdefs/__init__.py +28 -0
- smallworld/emulators/panda/machdefs/aarch64.py +93 -0
- smallworld/emulators/panda/machdefs/amd64.py +71 -0
- smallworld/emulators/panda/machdefs/arm.py +89 -0
- smallworld/emulators/panda/machdefs/i386.py +36 -0
- smallworld/emulators/panda/machdefs/machdef.py +86 -0
- smallworld/emulators/panda/machdefs/mips.py +94 -0
- smallworld/emulators/panda/machdefs/mips64.py +91 -0
- smallworld/emulators/panda/machdefs/ppc.py +79 -0
- smallworld/emulators/panda/panda.py +575 -0
- smallworld/emulators/unicorn/__init__.py +13 -0
- smallworld/emulators/unicorn/machdefs/__init__.py +28 -0
- smallworld/emulators/unicorn/machdefs/aarch64.py +310 -0
- smallworld/emulators/unicorn/machdefs/amd64.py +326 -0
- smallworld/emulators/unicorn/machdefs/arm.py +321 -0
- smallworld/emulators/unicorn/machdefs/i386.py +137 -0
- smallworld/emulators/unicorn/machdefs/machdef.py +117 -0
- smallworld/emulators/unicorn/machdefs/mips.py +202 -0
- smallworld/emulators/unicorn/unicorn.py +684 -0
- smallworld/exceptions/__init__.py +5 -0
- smallworld/exceptions/exceptions.py +85 -0
- smallworld/exceptions/unstable/__init__.py +1 -0
- smallworld/exceptions/unstable/exceptions.py +25 -0
- smallworld/extern/__init__.py +4 -0
- smallworld/extern/ctypes.py +94 -0
- smallworld/extern/unstable/__init__.py +1 -0
- smallworld/extern/unstable/ghidra.py +129 -0
- smallworld/helpers.py +107 -0
- smallworld/hinting/__init__.py +8 -0
- smallworld/hinting/hinting.py +214 -0
- smallworld/hinting/hints.py +427 -0
- smallworld/hinting/unstable/__init__.py +2 -0
- smallworld/hinting/utils.py +19 -0
- smallworld/instructions/__init__.py +18 -0
- smallworld/instructions/aarch64.py +20 -0
- smallworld/instructions/arm.py +18 -0
- smallworld/instructions/bsid.py +67 -0
- smallworld/instructions/instructions.py +258 -0
- smallworld/instructions/mips.py +21 -0
- smallworld/instructions/x86.py +100 -0
- smallworld/logging.py +90 -0
- smallworld/platforms.py +95 -0
- smallworld/py.typed +0 -0
- smallworld/state/__init__.py +6 -0
- smallworld/state/cpus/__init__.py +32 -0
- smallworld/state/cpus/aarch64.py +563 -0
- smallworld/state/cpus/amd64.py +676 -0
- smallworld/state/cpus/arm.py +630 -0
- smallworld/state/cpus/cpu.py +71 -0
- smallworld/state/cpus/i386.py +239 -0
- smallworld/state/cpus/mips.py +374 -0
- smallworld/state/cpus/mips64.py +372 -0
- smallworld/state/cpus/powerpc.py +229 -0
- smallworld/state/cpus/riscv.py +357 -0
- smallworld/state/cpus/xtensa.py +80 -0
- smallworld/state/memory/__init__.py +7 -0
- smallworld/state/memory/code.py +70 -0
- smallworld/state/memory/elf/__init__.py +3 -0
- smallworld/state/memory/elf/elf.py +564 -0
- smallworld/state/memory/elf/rela/__init__.py +32 -0
- smallworld/state/memory/elf/rela/aarch64.py +27 -0
- smallworld/state/memory/elf/rela/amd64.py +32 -0
- smallworld/state/memory/elf/rela/arm.py +51 -0
- smallworld/state/memory/elf/rela/i386.py +32 -0
- smallworld/state/memory/elf/rela/mips.py +45 -0
- smallworld/state/memory/elf/rela/ppc.py +45 -0
- smallworld/state/memory/elf/rela/rela.py +63 -0
- smallworld/state/memory/elf/rela/riscv64.py +27 -0
- smallworld/state/memory/elf/rela/xtensa.py +15 -0
- smallworld/state/memory/elf/structs.py +55 -0
- smallworld/state/memory/heap.py +85 -0
- smallworld/state/memory/memory.py +181 -0
- smallworld/state/memory/stack/__init__.py +31 -0
- smallworld/state/memory/stack/aarch64.py +22 -0
- smallworld/state/memory/stack/amd64.py +42 -0
- smallworld/state/memory/stack/arm.py +66 -0
- smallworld/state/memory/stack/i386.py +22 -0
- smallworld/state/memory/stack/mips.py +34 -0
- smallworld/state/memory/stack/mips64.py +34 -0
- smallworld/state/memory/stack/ppc.py +34 -0
- smallworld/state/memory/stack/riscv.py +22 -0
- smallworld/state/memory/stack/stack.py +127 -0
- smallworld/state/memory/stack/xtensa.py +34 -0
- smallworld/state/models/__init__.py +6 -0
- smallworld/state/models/mmio.py +186 -0
- smallworld/state/models/model.py +163 -0
- smallworld/state/models/posix.py +455 -0
- smallworld/state/models/x86/__init__.py +2 -0
- smallworld/state/models/x86/microsoftcdecl.py +35 -0
- smallworld/state/models/x86/systemv.py +240 -0
- smallworld/state/state.py +962 -0
- smallworld/state/unstable/__init__.py +0 -0
- smallworld/state/unstable/elf.py +393 -0
- smallworld/state/x86_registers.py +30 -0
- smallworld/utils.py +935 -0
- smallworld_re-1.0.0.dist-info/LICENSE.txt +21 -0
- smallworld_re-1.0.0.dist-info/METADATA +189 -0
- smallworld_re-1.0.0.dist-info/RECORD +166 -0
- smallworld_re-1.0.0.dist-info/WHEEL +5 -0
- smallworld_re-1.0.0.dist-info/entry_points.txt +2 -0
- smallworld_re-1.0.0.dist-info/top_level.txt +1 -0
smallworld/utils.py
ADDED
@@ -0,0 +1,935 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import abc
|
4
|
+
import inspect
|
5
|
+
import io
|
6
|
+
import typing
|
7
|
+
from collections.abc import Iterable
|
8
|
+
|
9
|
+
|
10
|
+
class MetadataMixin(metaclass=abc.ABCMeta):
|
11
|
+
@property
|
12
|
+
@abc.abstractmethod
|
13
|
+
def name(self) -> str:
|
14
|
+
"""The name of this analysis.
|
15
|
+
|
16
|
+
Names should be kebab-case, all lowercase, no whitespace for proper
|
17
|
+
formatting.
|
18
|
+
"""
|
19
|
+
pass
|
20
|
+
|
21
|
+
@property
|
22
|
+
@abc.abstractmethod
|
23
|
+
def description(self) -> str:
|
24
|
+
"""A description of this analysis.
|
25
|
+
|
26
|
+
Descriptions should be a single sentence, lowercase, with no final
|
27
|
+
punctuation for proper formatting.
|
28
|
+
"""
|
29
|
+
|
30
|
+
return ""
|
31
|
+
|
32
|
+
@property
|
33
|
+
@abc.abstractmethod
|
34
|
+
def version(self) -> str:
|
35
|
+
"""The version string for this analysis.
|
36
|
+
|
37
|
+
We recommend using `Semantic Versioning`_
|
38
|
+
|
39
|
+
.. _Semantic Versioning:
|
40
|
+
https://semver.org/
|
41
|
+
"""
|
42
|
+
|
43
|
+
return ""
|
44
|
+
|
45
|
+
|
46
|
+
def find_subclass(
|
47
|
+
cls: typing.Type[typing.Any],
|
48
|
+
check: typing.Callable[[typing.Type[typing.Any]], bool],
|
49
|
+
*args,
|
50
|
+
**kwargs,
|
51
|
+
):
|
52
|
+
"""Find and instantiate a subclass for dependency injection
|
53
|
+
|
54
|
+
This pattern involves finding an implementation by sematic criteria,
|
55
|
+
rather than explicity encoding a reference to it.
|
56
|
+
|
57
|
+
This makes for nice modular code, since the invoker
|
58
|
+
doesn't need to get updated every time a new module is added.
|
59
|
+
|
60
|
+
Arguments:
|
61
|
+
cls: The class representing the interface to search
|
62
|
+
check: Callable for testing the desired criteria
|
63
|
+
args: Any positional/variadic args to pass to the initializer
|
64
|
+
kwargs: Any keyword arguments to pass to the initializer
|
65
|
+
|
66
|
+
Returns: An instance of a subclass of cls matching the criteria from check
|
67
|
+
|
68
|
+
Raises:
|
69
|
+
ValueError: If no subclass of cls matches the criteria
|
70
|
+
"""
|
71
|
+
class_stack: typing.List[typing.Type[typing.Any]] = [cls]
|
72
|
+
while len(class_stack) > 0:
|
73
|
+
impl: typing.Type[typing.Any] = class_stack.pop(-1)
|
74
|
+
|
75
|
+
if not inspect.isabstract(impl) and check(impl):
|
76
|
+
return impl(*args, **kwargs)
|
77
|
+
# __subclasses__ is not transitive;
|
78
|
+
# need to call this on each sublclass to do a full traversal.
|
79
|
+
class_stack.extend(impl.__subclasses__())
|
80
|
+
|
81
|
+
raise ValueError(f"No instance of {cls} matching criteria")
|
82
|
+
|
83
|
+
|
84
|
+
class RBNode:
|
85
|
+
def __init__(self, key, value, nil):
|
86
|
+
self.key = key
|
87
|
+
self.value = value
|
88
|
+
self.is_black = False
|
89
|
+
self.parent = None
|
90
|
+
self.child = [nil, nil]
|
91
|
+
|
92
|
+
|
93
|
+
class RBTree(Iterable):
|
94
|
+
"""A self-balancing binary search tree
|
95
|
+
|
96
|
+
This implements a canonical red-black tree.
|
97
|
+
Values can be whatever you want, as long as all items
|
98
|
+
in the tree can use the same key function.
|
99
|
+
|
100
|
+
You can mutate values outside the tree,
|
101
|
+
as long as you don't change their keys.
|
102
|
+
|
103
|
+
Arguments:
|
104
|
+
key: Function for converting values into integers for comparison
|
105
|
+
"""
|
106
|
+
|
107
|
+
def __init__(self, key: typing.Callable[[typing.Any], int] = lambda x: x):
|
108
|
+
self._key = key
|
109
|
+
self._nil: RBNode = RBNode(0, None, None)
|
110
|
+
self._nil.is_black = True
|
111
|
+
self._root: RBNode = self._nil
|
112
|
+
|
113
|
+
def _rotate(self, P, branch):
|
114
|
+
G = P.parent
|
115
|
+
S = P.child[1 - branch]
|
116
|
+
|
117
|
+
if S is self._nil:
|
118
|
+
raise ValueError("Can't rotate; S is nil!")
|
119
|
+
|
120
|
+
C = S.child[branch]
|
121
|
+
|
122
|
+
P.child[1 - branch] = C
|
123
|
+
if C is not self._nil:
|
124
|
+
C.parent = P
|
125
|
+
|
126
|
+
S.child[branch] = P
|
127
|
+
P.parent = S
|
128
|
+
|
129
|
+
S.parent = G
|
130
|
+
|
131
|
+
if G is not None:
|
132
|
+
branch = 1 if P is G.child[1] else 0
|
133
|
+
G.child[branch] = S
|
134
|
+
else:
|
135
|
+
self._root = S
|
136
|
+
|
137
|
+
return S
|
138
|
+
|
139
|
+
def insert(self, value: typing.Any) -> None:
|
140
|
+
"""Insert a value into the tree
|
141
|
+
|
142
|
+
Arguments:
|
143
|
+
value: The value to insert
|
144
|
+
|
145
|
+
Raises:
|
146
|
+
ValueError: If there's a key collision between value and something in the tree
|
147
|
+
"""
|
148
|
+
N = RBNode(self._key(value), value, self._nil)
|
149
|
+
|
150
|
+
# Case 0: This is the first node ever
|
151
|
+
if self._root is self._nil:
|
152
|
+
self._root = N
|
153
|
+
return
|
154
|
+
|
155
|
+
# Insert into tree normally
|
156
|
+
P = self._root
|
157
|
+
while True:
|
158
|
+
if P.key > N.key:
|
159
|
+
branch = 0
|
160
|
+
elif P.key < N.key:
|
161
|
+
branch = 1
|
162
|
+
else:
|
163
|
+
raise ValueError(f"Key collision on {value}")
|
164
|
+
if P.child[branch] is self._nil:
|
165
|
+
break
|
166
|
+
P = P.child[branch]
|
167
|
+
N.parent = P
|
168
|
+
P.child[branch] = N
|
169
|
+
|
170
|
+
# Rebalance the tree iteratively
|
171
|
+
while P is not None:
|
172
|
+
if P.is_black:
|
173
|
+
# Case 1: P is black; we're all fine.
|
174
|
+
break
|
175
|
+
|
176
|
+
G = P.parent
|
177
|
+
if G is None:
|
178
|
+
# Case 4: P is the root and red
|
179
|
+
# Since N is red, make P black
|
180
|
+
P.is_black = True
|
181
|
+
break
|
182
|
+
|
183
|
+
# Find our uncle
|
184
|
+
branch = 0 if G.child[0] is P else 1
|
185
|
+
U = G.child[1 - branch]
|
186
|
+
|
187
|
+
if U.is_black:
|
188
|
+
if N is P.child[1 - branch]:
|
189
|
+
# Case 5: P is red, U is black, and N is an inner grandchild of G.
|
190
|
+
# Rotate the tree so it's an outer grandchild
|
191
|
+
self._rotate(P, branch)
|
192
|
+
N = P
|
193
|
+
P = G.child[branch]
|
194
|
+
# Case 6: P is red, U is black, N is an outer grandchild of G
|
195
|
+
# Rotate the tree to fix things
|
196
|
+
self._rotate(G, 1 - branch)
|
197
|
+
P.is_black = True
|
198
|
+
G.is_black = False
|
199
|
+
break
|
200
|
+
|
201
|
+
# Case 2: P and U are red
|
202
|
+
# Make P and U black, and G red
|
203
|
+
P.is_black = True
|
204
|
+
U.is_black = True
|
205
|
+
G.is_black = False
|
206
|
+
|
207
|
+
# Iterate one black level (2 tree levels) higher.
|
208
|
+
N = G
|
209
|
+
P = N.parent
|
210
|
+
|
211
|
+
# Case 3: N is now the root and red
|
212
|
+
# self._verify_rb(self._root)
|
213
|
+
return
|
214
|
+
|
215
|
+
def _get_node(self, value):
|
216
|
+
# Helper for fetching a node,
|
217
|
+
# or raising an error if we can't find it.
|
218
|
+
k = self._key(value)
|
219
|
+
N = self._root
|
220
|
+
while N is not self._nil:
|
221
|
+
if k == N.key:
|
222
|
+
if value == N.value:
|
223
|
+
return N
|
224
|
+
else:
|
225
|
+
raise ValueError(f"Key {k} had unexpected value {N.value}")
|
226
|
+
break
|
227
|
+
elif k < N.key:
|
228
|
+
N = N.child[0]
|
229
|
+
elif N.key < k:
|
230
|
+
N = N.child[1]
|
231
|
+
raise ValueError(f"Value {value} is not in the tree")
|
232
|
+
|
233
|
+
def _verify_rb(self, N, c=0):
|
234
|
+
# Verify the following properties:
|
235
|
+
#
|
236
|
+
# - Doubly-Linked Tree:
|
237
|
+
# - N.parent is None iff self._root is N
|
238
|
+
# - N in N.parent.child
|
239
|
+
#
|
240
|
+
# - Binary Search Tree:
|
241
|
+
# - N.child[0].key < N.key < N.child[1].key
|
242
|
+
#
|
243
|
+
# - Red-Black Tree:
|
244
|
+
# - Red nodes cannot have red children
|
245
|
+
# - All paths between root and nil contain the same number of black nodes
|
246
|
+
if N is self._nil:
|
247
|
+
return c + 1
|
248
|
+
if N.parent is None and N is not self._root:
|
249
|
+
raise Exception(f"DLT violation at {hex(N.key)}: Non-root has empty parent")
|
250
|
+
if N is self._root and N.parent is not None:
|
251
|
+
raise Exception(f"DLT violation at {hex(N.key)}: Root has non-empty parent")
|
252
|
+
L = N.child[0]
|
253
|
+
R = N.child[1]
|
254
|
+
if L is not self._nil:
|
255
|
+
if N.key <= L.key:
|
256
|
+
raise Exception(f"BST violation at {hex(N.key)}: Left is {hex(L.key)}")
|
257
|
+
if L.parent is not N:
|
258
|
+
raise Exception(
|
259
|
+
f"DLT violation at {hex(N.key)}: Left child {hex(L.key)} parent broken"
|
260
|
+
)
|
261
|
+
if not N.is_black and not L.is_black:
|
262
|
+
raise Exception(
|
263
|
+
f"RBT violation at {hex(N.key)}: N and left child {hex(L.key)} are red"
|
264
|
+
)
|
265
|
+
if R is not self._nil:
|
266
|
+
if N.key >= R.key:
|
267
|
+
raise Exception(f"BST violation at {hex(N.key)}: Right is {hex(R.key)}")
|
268
|
+
if R.parent is not N:
|
269
|
+
raise Exception(
|
270
|
+
f"DLT violation at {hex(N.key)}: Left child {R.key} parent broken"
|
271
|
+
)
|
272
|
+
if not N.is_black and not R.is_black:
|
273
|
+
raise Exception(
|
274
|
+
f"RBT violation at {hex(N.key)}: N and right child {hex(R.key)} are red"
|
275
|
+
)
|
276
|
+
c += 1 if N.is_black else 0
|
277
|
+
left_c = self._verify_rb(L, c)
|
278
|
+
right_c = self._verify_rb(R, c)
|
279
|
+
if left_c != right_c:
|
280
|
+
raise Exception(f"RBT violation at {hex(N.key)}: {left_c} vs {right_c}")
|
281
|
+
return left_c
|
282
|
+
|
283
|
+
def _remove_node(self, N):
|
284
|
+
if N.child[0] is not self._nil and N.child[1] is not self._nil:
|
285
|
+
# Case 1: N has 2 children
|
286
|
+
# Find in-order successor
|
287
|
+
S = N.child[1]
|
288
|
+
while S.child[0] is not self._nil:
|
289
|
+
S = S.child[0]
|
290
|
+
# Swap values
|
291
|
+
k = N.key
|
292
|
+
v = N.value
|
293
|
+
N.key = S.key
|
294
|
+
N.value = S.value
|
295
|
+
S.key = k
|
296
|
+
S.value = v
|
297
|
+
# Remove the successor
|
298
|
+
self._remove_node(S)
|
299
|
+
|
300
|
+
elif N.child[0] is not self._nil or N.child[1] is not self._nil:
|
301
|
+
# Case 2: N has 1 child
|
302
|
+
C = N.child[0] if N.child[0] is not self._nil else N.child[1]
|
303
|
+
# Make C black
|
304
|
+
C.is_black = True
|
305
|
+
|
306
|
+
if N.parent is None:
|
307
|
+
# Case 2a: N is the root; no parent
|
308
|
+
# C is now the root.
|
309
|
+
self._root = C
|
310
|
+
C.parent = None
|
311
|
+
else:
|
312
|
+
# Case 2b: N is not the root; parent
|
313
|
+
P = N.parent
|
314
|
+
branch = 0 if N is P.child[0] else 1
|
315
|
+
# Prune N from the tree
|
316
|
+
C.parent = P
|
317
|
+
P.child[branch] = C
|
318
|
+
|
319
|
+
elif N.parent is None:
|
320
|
+
# Case 3: N has no children and N is the root
|
321
|
+
# Make the root nil; we're empty
|
322
|
+
self._root = self._nil
|
323
|
+
|
324
|
+
elif not N.is_black:
|
325
|
+
# Case 4: N has no children and N is red
|
326
|
+
# Prune N out of the tree
|
327
|
+
P = N.parent
|
328
|
+
branch = 0 if P.child[0] is N else 1
|
329
|
+
P.child[branch] = self._nil
|
330
|
+
|
331
|
+
else:
|
332
|
+
# Case 5: N has no children and N is black
|
333
|
+
# Can't just delete the node; need to rebalance
|
334
|
+
P = N.parent
|
335
|
+
branch = 0 if P.child[0] is N else 1
|
336
|
+
P.child[branch] = self._nil
|
337
|
+
|
338
|
+
while P is not None:
|
339
|
+
# Find our sibling, distant nephew, and close nephew
|
340
|
+
S = P.child[1 - branch]
|
341
|
+
D = S.child[1 - branch]
|
342
|
+
C = S.child[branch]
|
343
|
+
|
344
|
+
if not S.is_black:
|
345
|
+
# Case 5.3: S is red; P, C, D must be black
|
346
|
+
self._rotate(P, branch)
|
347
|
+
|
348
|
+
P.is_black = False
|
349
|
+
S.is_black = True
|
350
|
+
|
351
|
+
S = C
|
352
|
+
D = S.child[1 - branch]
|
353
|
+
C = S.child[branch]
|
354
|
+
# S is now black; handle according to 5.4, 5.5 or 5.6
|
355
|
+
|
356
|
+
if D is not self._nil and not D.is_black:
|
357
|
+
# Case 5.6: S is black, D is red
|
358
|
+
self._rotate(P, branch)
|
359
|
+
S.is_black = P.is_black
|
360
|
+
P.is_black = True
|
361
|
+
D.is_black = True
|
362
|
+
break
|
363
|
+
|
364
|
+
if C is not self._nil and not C.is_black:
|
365
|
+
# Case 5.5: S is black, C is red
|
366
|
+
self._rotate(S, 1 - branch)
|
367
|
+
S.is_black = False
|
368
|
+
C.is_black = True
|
369
|
+
D = S
|
370
|
+
S = C
|
371
|
+
|
372
|
+
# Now S is red and D is black
|
373
|
+
# We match case 5.6
|
374
|
+
self._rotate(P, branch)
|
375
|
+
S.is_black = P.is_black
|
376
|
+
P.is_black = True
|
377
|
+
D.is_black = True
|
378
|
+
break
|
379
|
+
|
380
|
+
if not P.is_black:
|
381
|
+
# Case 5.4: P is red, S, C, and D are black
|
382
|
+
# Correct colors and we're done
|
383
|
+
S.is_black = False
|
384
|
+
P.is_black = True
|
385
|
+
break
|
386
|
+
|
387
|
+
S.is_black = False
|
388
|
+
N = P
|
389
|
+
P = N.parent
|
390
|
+
branch = 0 if P is None or P.child[0] is N else 1
|
391
|
+
|
392
|
+
# Case 5.1: N is the new root; we're done.
|
393
|
+
# self._verify_rb(self._root, 0)
|
394
|
+
return
|
395
|
+
|
396
|
+
def remove(self, value: typing.Any) -> None:
|
397
|
+
"""Remove a value from the tree
|
398
|
+
|
399
|
+
Arguments:
|
400
|
+
value: The value to remove
|
401
|
+
|
402
|
+
Raises:
|
403
|
+
ValueError: If `value` is not in the tree
|
404
|
+
"""
|
405
|
+
N = self._get_node(value)
|
406
|
+
self._remove_node(N)
|
407
|
+
|
408
|
+
def extend(self, iterable: Iterable) -> None:
|
409
|
+
"""Add all values from an iterable to this tree
|
410
|
+
|
411
|
+
Arguments:
|
412
|
+
iterable: Iterable containing the values you want to add
|
413
|
+
"""
|
414
|
+
for x in iterable:
|
415
|
+
self.insert(x)
|
416
|
+
|
417
|
+
def is_empty(self) -> bool:
|
418
|
+
"""Check if the tree is empty
|
419
|
+
|
420
|
+
Returns:
|
421
|
+
True if the tree is empty, else false.
|
422
|
+
"""
|
423
|
+
return self._root is self._nil
|
424
|
+
|
425
|
+
def contains(self, value: typing.Any) -> bool:
|
426
|
+
"""Check if the tree contains a value
|
427
|
+
|
428
|
+
Arguments:
|
429
|
+
value: The value to locate
|
430
|
+
|
431
|
+
Returns:
|
432
|
+
True if `value` is in the tree, else False
|
433
|
+
"""
|
434
|
+
try:
|
435
|
+
self._get_node(value)
|
436
|
+
return True
|
437
|
+
except:
|
438
|
+
return False
|
439
|
+
|
440
|
+
def bisect_left(self, value: typing.Any) -> typing.Optional[typing.Any]:
|
441
|
+
"""Find the value or its in-order predecessor
|
442
|
+
|
443
|
+
Arguments:
|
444
|
+
value: The value to search for
|
445
|
+
Returns:
|
446
|
+
- `value` if `value` is in the tree
|
447
|
+
- The value with the highest key less than that of `value`
|
448
|
+
- `None`, if there are no values with a key less than that of `value`
|
449
|
+
"""
|
450
|
+
k = self._key(value)
|
451
|
+
N = self._root
|
452
|
+
# Traverse the tree to find our match
|
453
|
+
while N is not self._nil:
|
454
|
+
if k == N.key:
|
455
|
+
return N.value
|
456
|
+
elif k < N.key:
|
457
|
+
if N.child[0] is self._nil:
|
458
|
+
break
|
459
|
+
N = N.child[0]
|
460
|
+
elif N.key < k:
|
461
|
+
if N.child[1] is self._nil:
|
462
|
+
break
|
463
|
+
N = N.child[1]
|
464
|
+
|
465
|
+
if k < N.key:
|
466
|
+
# Our match is to our right.
|
467
|
+
# We need its in-order predecessor
|
468
|
+
# This is the first ancestor for which our subtree is greater
|
469
|
+
P = N.parent
|
470
|
+
while P is not None:
|
471
|
+
branch = 0 if N is P.child[0] else 1
|
472
|
+
N = P
|
473
|
+
P = N.parent
|
474
|
+
if branch == 1:
|
475
|
+
break
|
476
|
+
if k < N.key:
|
477
|
+
# We are already the left-most value
|
478
|
+
return None
|
479
|
+
|
480
|
+
return N.value
|
481
|
+
|
482
|
+
def bisect_right(self, value: typing.Any) -> typing.Optional[typing.Any]:
|
483
|
+
"""Find the value or its in-order successor
|
484
|
+
|
485
|
+
Arguments:
|
486
|
+
value: The value to search for
|
487
|
+
Returns:
|
488
|
+
- `value` if `value` is in the tree
|
489
|
+
- The value with the lowest key greater than that of `value`
|
490
|
+
- `None`, if there are no values with a key greater than that of `value`
|
491
|
+
"""
|
492
|
+
k = self._key(value)
|
493
|
+
N = self._root
|
494
|
+
# Traverse the tree to find our match
|
495
|
+
while N is not self._nil:
|
496
|
+
if k == N.key:
|
497
|
+
return N.value
|
498
|
+
elif k < N.key:
|
499
|
+
if N.child[0] is self._nil:
|
500
|
+
break
|
501
|
+
N = N.child[0]
|
502
|
+
elif k > N.key:
|
503
|
+
if N.child[1] is self._nil:
|
504
|
+
break
|
505
|
+
N = N.child[1]
|
506
|
+
|
507
|
+
if k > N.key:
|
508
|
+
# Our match is to our left.
|
509
|
+
# We need its in-order successor
|
510
|
+
# This is the first ancestor for which our subtree is less
|
511
|
+
P = N.parent
|
512
|
+
while P is not None:
|
513
|
+
branch = 0 if N is P.child[0] else 1
|
514
|
+
N = P
|
515
|
+
P = N.parent
|
516
|
+
if branch == 0:
|
517
|
+
break
|
518
|
+
if k > N.key:
|
519
|
+
# We are already the right-most value
|
520
|
+
return None
|
521
|
+
return N.value
|
522
|
+
|
523
|
+
def _values(self, N):
|
524
|
+
if N is not self._nil:
|
525
|
+
for v in self._values(N.child[0]):
|
526
|
+
yield v
|
527
|
+
yield N.value
|
528
|
+
for v in self._values(N.child[1]):
|
529
|
+
yield v
|
530
|
+
|
531
|
+
def values(self) -> typing.Any:
|
532
|
+
"""Generate values in the tree in in-order order
|
533
|
+
|
534
|
+
Yields:
|
535
|
+
Each value in the tree in in-order order
|
536
|
+
"""
|
537
|
+
for v in self._values(self._root):
|
538
|
+
yield v
|
539
|
+
|
540
|
+
def __iter__(self):
|
541
|
+
return iter(self.values())
|
542
|
+
|
543
|
+
|
544
|
+
class RangeCollection(Iterable):
|
545
|
+
"""A collection of non-overlapping ranges"""
|
546
|
+
|
547
|
+
def __init__(self):
|
548
|
+
# Back with an RBTree keyed off the start of the range
|
549
|
+
self._ranges = RBTree(key=lambda x: x[0])
|
550
|
+
|
551
|
+
def is_empty(self) -> bool:
|
552
|
+
"""Check if this collection is empty
|
553
|
+
|
554
|
+
Returns:
|
555
|
+
True iff there are no ranges in this collection
|
556
|
+
"""
|
557
|
+
return self._ranges.is_empty()
|
558
|
+
|
559
|
+
def contains(self, arange: typing.Tuple[int, int]) -> bool:
|
560
|
+
"""Check if a specific range overlaps any range in this collection
|
561
|
+
|
562
|
+
Arguments:
|
563
|
+
arange: The range to test for overlaps
|
564
|
+
|
565
|
+
Returns:
|
566
|
+
True iff at least one range in the collection covers at least one value in `arange`
|
567
|
+
"""
|
568
|
+
|
569
|
+
start, end = arange
|
570
|
+
lo = self._ranges.bisect_left(arange)
|
571
|
+
if lo is not None:
|
572
|
+
# There is something before start
|
573
|
+
lo_start, lo_end = lo
|
574
|
+
# lo_start is going to be less than or equal to start.
|
575
|
+
# If range overlaps, start must be in lo
|
576
|
+
if start < lo_end:
|
577
|
+
return True
|
578
|
+
|
579
|
+
hi = self._ranges.bisect_right(arange)
|
580
|
+
if hi is not None:
|
581
|
+
# There is something after start
|
582
|
+
hi_start, hi_end = hi
|
583
|
+
# hi_start is going to be greater than or equal to start
|
584
|
+
# If range overlaps, hi_start must be in arange.
|
585
|
+
if hi_start < end:
|
586
|
+
return True
|
587
|
+
|
588
|
+
return False
|
589
|
+
|
590
|
+
def contains_value(self, value: int) -> bool:
|
591
|
+
"""Check if any range in this collection contains a value
|
592
|
+
|
593
|
+
Arguments:
|
594
|
+
value: The value to locate
|
595
|
+
|
596
|
+
Returns:
|
597
|
+
True iff there is a range in the collection which contains `value`
|
598
|
+
"""
|
599
|
+
arange = (value, value + 1)
|
600
|
+
|
601
|
+
# Only need to test left bisect
|
602
|
+
# Value must equal start or be to the right
|
603
|
+
lo = self._ranges.bisect_left(arange)
|
604
|
+
if lo is not None:
|
605
|
+
lo_start, lo_end = lo
|
606
|
+
if value >= lo_start and value < lo_end:
|
607
|
+
return True
|
608
|
+
|
609
|
+
return False
|
610
|
+
|
611
|
+
def find_closest_range(
|
612
|
+
self, value: int
|
613
|
+
) -> typing.Tuple[typing.Optional[typing.Tuple[int, int]], bool]:
|
614
|
+
"""Find the range closest to a value
|
615
|
+
|
616
|
+
Arguments:
|
617
|
+
value: The value to locate
|
618
|
+
|
619
|
+
Returns:
|
620
|
+
- The closest range, or None if the collection is empty
|
621
|
+
- True iff `value` is in that range
|
622
|
+
"""
|
623
|
+
out = self._ranges.bisect_left((value, value + 1))
|
624
|
+
if out is None:
|
625
|
+
out = self._ranges.bisect_right((value, value + 1))
|
626
|
+
return (out, out is not None and out[0] <= value < out[1])
|
627
|
+
|
628
|
+
def add_range(self, arange: typing.Tuple[int, int]) -> None:
|
629
|
+
"""Add a range to the collection
|
630
|
+
|
631
|
+
Arguments:
|
632
|
+
arange: The range to insert
|
633
|
+
"""
|
634
|
+
start, end = arange
|
635
|
+
if start >= end:
|
636
|
+
raise ValueError(f"Invalid range {arange}")
|
637
|
+
|
638
|
+
lo = self._ranges.bisect_left(arange)
|
639
|
+
|
640
|
+
if lo is not None:
|
641
|
+
# We are not the lowest range
|
642
|
+
lo_start, lo_end = lo
|
643
|
+
if lo_start == start and lo_end == end:
|
644
|
+
# We are already in the collection. Happy happy joy joy.
|
645
|
+
return
|
646
|
+
# There's at least one range below us.
|
647
|
+
# Since the tree keys off the start of ranges,
|
648
|
+
# we can only collide with this one element
|
649
|
+
if start >= lo_start and start <= lo_end:
|
650
|
+
# We collide with lo.
|
651
|
+
# Remove it from the tree, and absorb it into ourself.
|
652
|
+
self._ranges.remove(lo)
|
653
|
+
if lo_start < start:
|
654
|
+
start = lo_start
|
655
|
+
if lo_end > end:
|
656
|
+
end = lo_end
|
657
|
+
|
658
|
+
hi = self._ranges.bisect_right(arange)
|
659
|
+
while hi is not None:
|
660
|
+
hi_start, hi_end = hi
|
661
|
+
if hi_start > end:
|
662
|
+
# We don't overlap with hi.
|
663
|
+
# We can stop slurping things up.
|
664
|
+
break
|
665
|
+
if hi_start < start:
|
666
|
+
start = hi_start
|
667
|
+
if hi_end > end:
|
668
|
+
end = hi_end
|
669
|
+
self._ranges.remove(hi)
|
670
|
+
hi = self._ranges.bisect_right(arange)
|
671
|
+
|
672
|
+
self._ranges.insert((start, end))
|
673
|
+
|
674
|
+
def update(self, other: RangeCollection) -> None:
|
675
|
+
"""Add all ranges from another collection to this collection
|
676
|
+
|
677
|
+
Arguments:
|
678
|
+
other: The collection containing ranges to add
|
679
|
+
"""
|
680
|
+
for rng in other:
|
681
|
+
self.add_range(rng)
|
682
|
+
|
683
|
+
def add_value(self, value: int) -> None:
|
684
|
+
"""Add a singleton range to the collection
|
685
|
+
|
686
|
+
Arguments:
|
687
|
+
value: The value for which to add a singleton
|
688
|
+
"""
|
689
|
+
self.add_range((value, value + 1))
|
690
|
+
|
691
|
+
def remove_range(self, arange: typing.Tuple[int, int]) -> None:
|
692
|
+
"""Remove any overlaps between a specific range and this collection.
|
693
|
+
|
694
|
+
This doesn't remove a specific range;
|
695
|
+
rather, it removes any intersections between items
|
696
|
+
of this collection and `arange`.
|
697
|
+
|
698
|
+
Arguments:
|
699
|
+
arange: The range to remove
|
700
|
+
"""
|
701
|
+
start, end = arange
|
702
|
+
if start >= end:
|
703
|
+
raise ValueError(f"Invalid range {arange}")
|
704
|
+
lo = self._ranges.bisect_left(arange)
|
705
|
+
if lo is not None:
|
706
|
+
# We are not the lowest range
|
707
|
+
lo_start, lo_end = lo
|
708
|
+
if lo_start == start and lo_end == lo:
|
709
|
+
# We exactly match an existing range
|
710
|
+
self._ranges.remove(arange)
|
711
|
+
return
|
712
|
+
if start >= lo_start and end <= lo_end:
|
713
|
+
# We collide with lo.
|
714
|
+
# Remove lo and add the remainder back
|
715
|
+
self._ranges.remove(lo)
|
716
|
+
if start > lo_start:
|
717
|
+
# There's a bit at the beginning we need to replace
|
718
|
+
self._ranges.insert((lo_start, start))
|
719
|
+
if end < lo_end:
|
720
|
+
# There's a bit at the end we need to replace
|
721
|
+
self._ranges.insert((end, lo_end))
|
722
|
+
# We don't need to keep going; everything will be higher.
|
723
|
+
return
|
724
|
+
|
725
|
+
hi = self._ranges.bisect_right(arange)
|
726
|
+
while hi is not None:
|
727
|
+
hi_start, hi_end = hi
|
728
|
+
if hi_start >= end:
|
729
|
+
# We don't overlap with hi; we're out of ranges
|
730
|
+
break
|
731
|
+
self._ranges.remove(hi)
|
732
|
+
if end < hi_end:
|
733
|
+
# There's a bit left over at the end
|
734
|
+
self._ranges.insert((end, hi_end))
|
735
|
+
# We don't need to keep going; everything else will be higher.
|
736
|
+
return
|
737
|
+
hi = self._ranges.bisect_right(arange)
|
738
|
+
|
739
|
+
def get_missing_ranges(
|
740
|
+
self, arange: typing.Tuple[int, int]
|
741
|
+
) -> typing.List[typing.Tuple[int, int]]:
|
742
|
+
"""Find the subset of a given range not covered by this collection"""
|
743
|
+
out = list()
|
744
|
+
start, end = arange
|
745
|
+
|
746
|
+
lo = self._ranges.bisect_left((start, end))
|
747
|
+
if lo is not None:
|
748
|
+
# There is an item below us
|
749
|
+
lo_start, lo_end = lo
|
750
|
+
# lo_start will be less than or equal to start,
|
751
|
+
# so there can't be a missing range before lo.
|
752
|
+
# We do care if there's an overlap
|
753
|
+
if lo_end > start:
|
754
|
+
# arange and lo overlap. Remove the overlap
|
755
|
+
start = lo_end
|
756
|
+
|
757
|
+
hi = self._ranges.bisect_right((start, end))
|
758
|
+
while hi is not None:
|
759
|
+
# There is an item above us
|
760
|
+
hi_start, hi_end = hi
|
761
|
+
if hi_start >= end:
|
762
|
+
# The item is so far above that it can't intersect
|
763
|
+
# Anything else will be higher, so we're done.
|
764
|
+
break
|
765
|
+
if hi_start > start:
|
766
|
+
# hi doesn't cover the start of arange
|
767
|
+
# We found a missing range
|
768
|
+
out.append((start, hi_start))
|
769
|
+
start = hi_end
|
770
|
+
hi = self._ranges.bisect_right((start, end))
|
771
|
+
if start < end:
|
772
|
+
# There's still a bit left
|
773
|
+
out.append((start, end))
|
774
|
+
return out
|
775
|
+
|
776
|
+
@property
|
777
|
+
def ranges(self) -> typing.List[typing.Tuple[int, int]]:
|
778
|
+
"""The list of ranges in order by start"""
|
779
|
+
return list(self._ranges.values())
|
780
|
+
|
781
|
+
def __iter__(self):
|
782
|
+
return iter(self._ranges.values())
|
783
|
+
|
784
|
+
|
785
|
+
class SparseIO(io.BufferedIOBase):
|
786
|
+
"""Sparse memory-backed IO stream object
|
787
|
+
|
788
|
+
BytesIO requires a contiguous bytes object.
|
789
|
+
If you have a large, sparse memory image,
|
790
|
+
it will gladly OOM your analysis.
|
791
|
+
|
792
|
+
This uses an RBTree in the same manner as
|
793
|
+
RangeCollection to maintain a non-contiguous
|
794
|
+
sequence of bytes.
|
795
|
+
Any data outside those ranges is assumed to be zero.
|
796
|
+
|
797
|
+
This is used by AngrEmulator to load programs,
|
798
|
+
and makes it possible to load sparse memory images
|
799
|
+
covering large swaths of the address space into CLE.
|
800
|
+
"""
|
801
|
+
|
802
|
+
def __init__(self):
|
803
|
+
self._ranges = RBTree(key=lambda x: x[0])
|
804
|
+
self._pos = 0
|
805
|
+
self.size = 0
|
806
|
+
|
807
|
+
def seekable(self) -> bool:
|
808
|
+
return True
|
809
|
+
|
810
|
+
def readable(self) -> bool:
|
811
|
+
return True
|
812
|
+
|
813
|
+
def writable(self) -> bool:
|
814
|
+
return True
|
815
|
+
|
816
|
+
def seek(self, pos: int, whence: int = 0) -> int:
|
817
|
+
if not isinstance(pos, int):
|
818
|
+
raise TypeError(f"pos must be an int; got a {type(pos)}")
|
819
|
+
|
820
|
+
if not isinstance(whence, int):
|
821
|
+
raise TypeError(f"whence must be an int; got a {type(whence)}")
|
822
|
+
|
823
|
+
if whence < 0 or whence > 2:
|
824
|
+
raise ValueError(f"Invalid whence {whence}")
|
825
|
+
|
826
|
+
if whence == 0:
|
827
|
+
# Relative to start of file
|
828
|
+
self._pos = pos
|
829
|
+
elif whence == 1:
|
830
|
+
# Relative to current file
|
831
|
+
self._pos += pos
|
832
|
+
else:
|
833
|
+
# Relative to end of file
|
834
|
+
self._pos = self.size + pos
|
835
|
+
|
836
|
+
return self._pos
|
837
|
+
|
838
|
+
def read(self, size: typing.Optional[int] = -1) -> bytes:
|
839
|
+
if size is None or size == -1:
|
840
|
+
size = self.size
|
841
|
+
start = self._pos
|
842
|
+
end = start + size
|
843
|
+
data = bytearray()
|
844
|
+
|
845
|
+
lo = self._ranges.bisect_left((start, end, None))
|
846
|
+
if lo is not None:
|
847
|
+
# We are not below the lowest segment
|
848
|
+
lo_start, lo_end, lo_data = lo
|
849
|
+
if lo_end > start:
|
850
|
+
# We overlap lo
|
851
|
+
# lo_start is guaranteed to be less than or equal to start
|
852
|
+
a = start - lo_start
|
853
|
+
data += lo_data[a:]
|
854
|
+
start = lo_end
|
855
|
+
|
856
|
+
hi = self._ranges.bisect_right((start, end, None))
|
857
|
+
while hi is not None:
|
858
|
+
# We are not the right-most
|
859
|
+
hi_start, hi_end, hi_data = hi
|
860
|
+
if hi_start >= end:
|
861
|
+
break
|
862
|
+
if hi_start > start:
|
863
|
+
data += b"\x00" * (hi_start - start)
|
864
|
+
start = hi_start
|
865
|
+
a = min(hi_end, end) - hi_start
|
866
|
+
data += hi_data[:a]
|
867
|
+
start = a + hi_start
|
868
|
+
hi = self._ranges.bisect_right((start, end, None))
|
869
|
+
|
870
|
+
if start < end:
|
871
|
+
data += b"\x00" * (end - start)
|
872
|
+
|
873
|
+
self._pos = end
|
874
|
+
return bytes(data)
|
875
|
+
|
876
|
+
def read1(self, size: int = -1) -> bytes:
|
877
|
+
return self.read(size=size)
|
878
|
+
|
879
|
+
def write(self, data) -> int:
|
880
|
+
# NOTE: `data` is a bytes-like.
|
881
|
+
# Python doesn't add a way to annotate bytes-like types
|
882
|
+
# until 3.12
|
883
|
+
data = bytearray(data)
|
884
|
+
start = self._pos
|
885
|
+
end = start + len(data)
|
886
|
+
o_start = start
|
887
|
+
o_end = end
|
888
|
+
|
889
|
+
lo = self._ranges.bisect_left((start, end))
|
890
|
+
if lo is not None:
|
891
|
+
# We are not the lowest segment
|
892
|
+
lo_start, lo_end, lo_data = lo
|
893
|
+
if lo_end > start:
|
894
|
+
# We overlap lo
|
895
|
+
# Because of how bisect works here,
|
896
|
+
# lo_start must be less or equal to start
|
897
|
+
self._ranges.remove(lo)
|
898
|
+
|
899
|
+
a = start - lo_start
|
900
|
+
b = end - lo_start
|
901
|
+
|
902
|
+
lo_data[a:b] = data
|
903
|
+
data = lo_data
|
904
|
+
|
905
|
+
start = lo_start
|
906
|
+
if lo_end > end:
|
907
|
+
end = lo_end
|
908
|
+
|
909
|
+
hi = self._ranges.bisect_right((start, end))
|
910
|
+
while hi is not None:
|
911
|
+
hi_start, hi_end, hi_data = hi
|
912
|
+
# We are not the highest segment
|
913
|
+
if hi_start >= end:
|
914
|
+
# We do not overlap hi
|
915
|
+
break
|
916
|
+
|
917
|
+
# We overlap hi
|
918
|
+
# Because of how bisect works here,
|
919
|
+
# hi_start must be greater or equal to start
|
920
|
+
self._ranges.remove(hi)
|
921
|
+
if hi_end > end:
|
922
|
+
a = end - hi_start
|
923
|
+
data += hi_data[a:]
|
924
|
+
end = hi_end
|
925
|
+
hi = self._ranges.bisect_right((start, end))
|
926
|
+
|
927
|
+
if len(data) != end - start:
|
928
|
+
raise Exception(
|
929
|
+
f"Buffer contains {len(data)} bytes, but have ({hex(start)}, {hex(end)}), starting with ({hex(o_start)}, {hex(o_end)})"
|
930
|
+
)
|
931
|
+
if end > self.size:
|
932
|
+
self.size = end
|
933
|
+
self._pos = end
|
934
|
+
self._ranges.insert((start, end, data))
|
935
|
+
return len(data)
|