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.
Files changed (166) hide show
  1. smallworld/__init__.py +35 -0
  2. smallworld/analyses/__init__.py +14 -0
  3. smallworld/analyses/analysis.py +88 -0
  4. smallworld/analyses/code_coverage.py +31 -0
  5. smallworld/analyses/colorizer.py +682 -0
  6. smallworld/analyses/colorizer_summary.py +100 -0
  7. smallworld/analyses/field_detection/__init__.py +14 -0
  8. smallworld/analyses/field_detection/field_analysis.py +536 -0
  9. smallworld/analyses/field_detection/guards.py +26 -0
  10. smallworld/analyses/field_detection/hints.py +133 -0
  11. smallworld/analyses/field_detection/malloc.py +211 -0
  12. smallworld/analyses/forced_exec/__init__.py +3 -0
  13. smallworld/analyses/forced_exec/forced_exec.py +87 -0
  14. smallworld/analyses/underlays/__init__.py +4 -0
  15. smallworld/analyses/underlays/basic.py +13 -0
  16. smallworld/analyses/underlays/underlay.py +31 -0
  17. smallworld/analyses/unstable/__init__.py +4 -0
  18. smallworld/analyses/unstable/angr/__init__.py +0 -0
  19. smallworld/analyses/unstable/angr/base.py +12 -0
  20. smallworld/analyses/unstable/angr/divergence.py +274 -0
  21. smallworld/analyses/unstable/angr/model.py +383 -0
  22. smallworld/analyses/unstable/angr/nwbt.py +63 -0
  23. smallworld/analyses/unstable/angr/typedefs.py +170 -0
  24. smallworld/analyses/unstable/angr/utils.py +25 -0
  25. smallworld/analyses/unstable/angr/visitor.py +315 -0
  26. smallworld/analyses/unstable/angr_nwbt.py +106 -0
  27. smallworld/analyses/unstable/code_coverage.py +54 -0
  28. smallworld/analyses/unstable/code_reachable.py +44 -0
  29. smallworld/analyses/unstable/control_flow_tracer.py +71 -0
  30. smallworld/analyses/unstable/pointer_finder.py +90 -0
  31. smallworld/arch/__init__.py +0 -0
  32. smallworld/arch/aarch64_arch.py +286 -0
  33. smallworld/arch/amd64_arch.py +86 -0
  34. smallworld/arch/i386_arch.py +44 -0
  35. smallworld/emulators/__init__.py +14 -0
  36. smallworld/emulators/angr/__init__.py +7 -0
  37. smallworld/emulators/angr/angr.py +1652 -0
  38. smallworld/emulators/angr/default.py +15 -0
  39. smallworld/emulators/angr/exceptions.py +7 -0
  40. smallworld/emulators/angr/exploration/__init__.py +9 -0
  41. smallworld/emulators/angr/exploration/bounds.py +27 -0
  42. smallworld/emulators/angr/exploration/default.py +17 -0
  43. smallworld/emulators/angr/exploration/terminate.py +22 -0
  44. smallworld/emulators/angr/factory.py +55 -0
  45. smallworld/emulators/angr/machdefs/__init__.py +35 -0
  46. smallworld/emulators/angr/machdefs/aarch64.py +292 -0
  47. smallworld/emulators/angr/machdefs/amd64.py +192 -0
  48. smallworld/emulators/angr/machdefs/arm.py +387 -0
  49. smallworld/emulators/angr/machdefs/i386.py +221 -0
  50. smallworld/emulators/angr/machdefs/machdef.py +138 -0
  51. smallworld/emulators/angr/machdefs/mips.py +184 -0
  52. smallworld/emulators/angr/machdefs/mips64.py +189 -0
  53. smallworld/emulators/angr/machdefs/ppc.py +101 -0
  54. smallworld/emulators/angr/machdefs/riscv.py +261 -0
  55. smallworld/emulators/angr/machdefs/xtensa.py +255 -0
  56. smallworld/emulators/angr/memory/__init__.py +7 -0
  57. smallworld/emulators/angr/memory/default.py +10 -0
  58. smallworld/emulators/angr/memory/fixups.py +43 -0
  59. smallworld/emulators/angr/memory/memtrack.py +105 -0
  60. smallworld/emulators/angr/scratch.py +43 -0
  61. smallworld/emulators/angr/simos.py +53 -0
  62. smallworld/emulators/angr/utils.py +70 -0
  63. smallworld/emulators/emulator.py +1013 -0
  64. smallworld/emulators/hookable.py +252 -0
  65. smallworld/emulators/panda/__init__.py +5 -0
  66. smallworld/emulators/panda/machdefs/__init__.py +28 -0
  67. smallworld/emulators/panda/machdefs/aarch64.py +93 -0
  68. smallworld/emulators/panda/machdefs/amd64.py +71 -0
  69. smallworld/emulators/panda/machdefs/arm.py +89 -0
  70. smallworld/emulators/panda/machdefs/i386.py +36 -0
  71. smallworld/emulators/panda/machdefs/machdef.py +86 -0
  72. smallworld/emulators/panda/machdefs/mips.py +94 -0
  73. smallworld/emulators/panda/machdefs/mips64.py +91 -0
  74. smallworld/emulators/panda/machdefs/ppc.py +79 -0
  75. smallworld/emulators/panda/panda.py +575 -0
  76. smallworld/emulators/unicorn/__init__.py +13 -0
  77. smallworld/emulators/unicorn/machdefs/__init__.py +28 -0
  78. smallworld/emulators/unicorn/machdefs/aarch64.py +310 -0
  79. smallworld/emulators/unicorn/machdefs/amd64.py +326 -0
  80. smallworld/emulators/unicorn/machdefs/arm.py +321 -0
  81. smallworld/emulators/unicorn/machdefs/i386.py +137 -0
  82. smallworld/emulators/unicorn/machdefs/machdef.py +117 -0
  83. smallworld/emulators/unicorn/machdefs/mips.py +202 -0
  84. smallworld/emulators/unicorn/unicorn.py +684 -0
  85. smallworld/exceptions/__init__.py +5 -0
  86. smallworld/exceptions/exceptions.py +85 -0
  87. smallworld/exceptions/unstable/__init__.py +1 -0
  88. smallworld/exceptions/unstable/exceptions.py +25 -0
  89. smallworld/extern/__init__.py +4 -0
  90. smallworld/extern/ctypes.py +94 -0
  91. smallworld/extern/unstable/__init__.py +1 -0
  92. smallworld/extern/unstable/ghidra.py +129 -0
  93. smallworld/helpers.py +107 -0
  94. smallworld/hinting/__init__.py +8 -0
  95. smallworld/hinting/hinting.py +214 -0
  96. smallworld/hinting/hints.py +427 -0
  97. smallworld/hinting/unstable/__init__.py +2 -0
  98. smallworld/hinting/utils.py +19 -0
  99. smallworld/instructions/__init__.py +18 -0
  100. smallworld/instructions/aarch64.py +20 -0
  101. smallworld/instructions/arm.py +18 -0
  102. smallworld/instructions/bsid.py +67 -0
  103. smallworld/instructions/instructions.py +258 -0
  104. smallworld/instructions/mips.py +21 -0
  105. smallworld/instructions/x86.py +100 -0
  106. smallworld/logging.py +90 -0
  107. smallworld/platforms.py +95 -0
  108. smallworld/py.typed +0 -0
  109. smallworld/state/__init__.py +6 -0
  110. smallworld/state/cpus/__init__.py +32 -0
  111. smallworld/state/cpus/aarch64.py +563 -0
  112. smallworld/state/cpus/amd64.py +676 -0
  113. smallworld/state/cpus/arm.py +630 -0
  114. smallworld/state/cpus/cpu.py +71 -0
  115. smallworld/state/cpus/i386.py +239 -0
  116. smallworld/state/cpus/mips.py +374 -0
  117. smallworld/state/cpus/mips64.py +372 -0
  118. smallworld/state/cpus/powerpc.py +229 -0
  119. smallworld/state/cpus/riscv.py +357 -0
  120. smallworld/state/cpus/xtensa.py +80 -0
  121. smallworld/state/memory/__init__.py +7 -0
  122. smallworld/state/memory/code.py +70 -0
  123. smallworld/state/memory/elf/__init__.py +3 -0
  124. smallworld/state/memory/elf/elf.py +564 -0
  125. smallworld/state/memory/elf/rela/__init__.py +32 -0
  126. smallworld/state/memory/elf/rela/aarch64.py +27 -0
  127. smallworld/state/memory/elf/rela/amd64.py +32 -0
  128. smallworld/state/memory/elf/rela/arm.py +51 -0
  129. smallworld/state/memory/elf/rela/i386.py +32 -0
  130. smallworld/state/memory/elf/rela/mips.py +45 -0
  131. smallworld/state/memory/elf/rela/ppc.py +45 -0
  132. smallworld/state/memory/elf/rela/rela.py +63 -0
  133. smallworld/state/memory/elf/rela/riscv64.py +27 -0
  134. smallworld/state/memory/elf/rela/xtensa.py +15 -0
  135. smallworld/state/memory/elf/structs.py +55 -0
  136. smallworld/state/memory/heap.py +85 -0
  137. smallworld/state/memory/memory.py +181 -0
  138. smallworld/state/memory/stack/__init__.py +31 -0
  139. smallworld/state/memory/stack/aarch64.py +22 -0
  140. smallworld/state/memory/stack/amd64.py +42 -0
  141. smallworld/state/memory/stack/arm.py +66 -0
  142. smallworld/state/memory/stack/i386.py +22 -0
  143. smallworld/state/memory/stack/mips.py +34 -0
  144. smallworld/state/memory/stack/mips64.py +34 -0
  145. smallworld/state/memory/stack/ppc.py +34 -0
  146. smallworld/state/memory/stack/riscv.py +22 -0
  147. smallworld/state/memory/stack/stack.py +127 -0
  148. smallworld/state/memory/stack/xtensa.py +34 -0
  149. smallworld/state/models/__init__.py +6 -0
  150. smallworld/state/models/mmio.py +186 -0
  151. smallworld/state/models/model.py +163 -0
  152. smallworld/state/models/posix.py +455 -0
  153. smallworld/state/models/x86/__init__.py +2 -0
  154. smallworld/state/models/x86/microsoftcdecl.py +35 -0
  155. smallworld/state/models/x86/systemv.py +240 -0
  156. smallworld/state/state.py +962 -0
  157. smallworld/state/unstable/__init__.py +0 -0
  158. smallworld/state/unstable/elf.py +393 -0
  159. smallworld/state/x86_registers.py +30 -0
  160. smallworld/utils.py +935 -0
  161. smallworld_re-1.0.0.dist-info/LICENSE.txt +21 -0
  162. smallworld_re-1.0.0.dist-info/METADATA +189 -0
  163. smallworld_re-1.0.0.dist-info/RECORD +166 -0
  164. smallworld_re-1.0.0.dist-info/WHEEL +5 -0
  165. smallworld_re-1.0.0.dist-info/entry_points.txt +2 -0
  166. 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)