angr 9.2.139__py3-none-manylinux2014_aarch64.whl → 9.2.141__py3-none-manylinux2014_aarch64.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.
Potentially problematic release.
This version of angr might be problematic. Click here for more details.
- angr/__init__.py +1 -1
- angr/analyses/calling_convention/calling_convention.py +136 -53
- angr/analyses/calling_convention/fact_collector.py +44 -18
- angr/analyses/calling_convention/utils.py +3 -1
- angr/analyses/cfg/cfg_base.py +13 -0
- angr/analyses/cfg/cfg_fast.py +11 -0
- angr/analyses/cfg/indirect_jump_resolvers/jumptable.py +9 -8
- angr/analyses/decompiler/ail_simplifier.py +115 -72
- angr/analyses/decompiler/callsite_maker.py +24 -11
- angr/analyses/decompiler/clinic.py +78 -43
- angr/analyses/decompiler/decompiler.py +18 -7
- angr/analyses/decompiler/expression_narrower.py +1 -1
- angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
- angr/analyses/decompiler/optimization_passes/duplication_reverter/duplication_reverter.py +3 -1
- angr/analyses/decompiler/optimization_passes/flip_boolean_cmp.py +21 -2
- angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +84 -15
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +92 -11
- angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +53 -9
- angr/analyses/decompiler/peephole_optimizations/eager_eval.py +44 -7
- angr/analyses/decompiler/region_identifier.py +6 -4
- angr/analyses/decompiler/region_simplifiers/expr_folding.py +287 -122
- angr/analyses/decompiler/region_simplifiers/region_simplifier.py +31 -13
- angr/analyses/decompiler/ssailification/rewriting.py +23 -15
- angr/analyses/decompiler/ssailification/rewriting_engine.py +105 -24
- angr/analyses/decompiler/ssailification/ssailification.py +22 -14
- angr/analyses/decompiler/structured_codegen/c.py +73 -137
- angr/analyses/decompiler/structuring/dream.py +22 -18
- angr/analyses/decompiler/structuring/phoenix.py +158 -41
- angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
- angr/analyses/decompiler/structuring/structurer_base.py +37 -10
- angr/analyses/decompiler/structuring/structurer_nodes.py +4 -1
- angr/analyses/decompiler/utils.py +106 -21
- angr/analyses/deobfuscator/api_obf_finder.py +8 -5
- angr/analyses/deobfuscator/api_obf_type2_finder.py +18 -10
- angr/analyses/deobfuscator/string_obf_finder.py +105 -18
- angr/analyses/forward_analysis/forward_analysis.py +1 -1
- angr/analyses/propagator/top_checker_mixin.py +6 -6
- angr/analyses/reaching_definitions/__init__.py +2 -1
- angr/analyses/reaching_definitions/dep_graph.py +1 -12
- angr/analyses/reaching_definitions/engine_vex.py +36 -31
- angr/analyses/reaching_definitions/function_handler.py +15 -2
- angr/analyses/reaching_definitions/rd_state.py +1 -37
- angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
- angr/analyses/s_propagator.py +6 -41
- angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
- angr/analyses/s_reaching_definitions/s_rda_view.py +43 -25
- angr/analyses/stack_pointer_tracker.py +36 -22
- angr/analyses/typehoon/simple_solver.py +45 -7
- angr/analyses/typehoon/typeconsts.py +18 -5
- angr/analyses/variable_recovery/engine_ail.py +1 -1
- angr/analyses/variable_recovery/engine_base.py +7 -5
- angr/analyses/variable_recovery/engine_vex.py +20 -4
- angr/block.py +69 -107
- angr/callable.py +14 -7
- angr/calling_conventions.py +30 -11
- angr/distributed/__init__.py +1 -1
- angr/engines/__init__.py +7 -8
- angr/engines/engine.py +1 -120
- angr/engines/failure.py +2 -2
- angr/engines/hook.py +2 -2
- angr/engines/light/engine.py +2 -2
- angr/engines/pcode/engine.py +2 -14
- angr/engines/procedure.py +2 -2
- angr/engines/soot/engine.py +2 -2
- angr/engines/soot/statements/switch.py +1 -1
- angr/engines/successors.py +124 -11
- angr/engines/syscall.py +2 -2
- angr/engines/unicorn.py +3 -3
- angr/engines/vex/heavy/heavy.py +3 -15
- angr/factory.py +12 -22
- angr/knowledge_plugins/key_definitions/atoms.py +8 -4
- angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
- angr/knowledge_plugins/variables/variable_manager.py +7 -5
- angr/sim_type.py +19 -17
- angr/simos/simos.py +3 -1
- angr/state_plugins/plugin.py +19 -4
- angr/storage/memory_mixins/memory_mixin.py +1 -1
- angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
- angr/utils/ssa/__init__.py +119 -4
- angr/utils/types.py +48 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/METADATA +6 -6
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/RECORD +87 -86
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/LICENSE +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/WHEEL +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/entry_points.txt +0 -0
- {angr-9.2.139.dist-info → angr-9.2.141.dist-info}/top_level.txt +0 -0
|
@@ -9,11 +9,11 @@ import networkx
|
|
|
9
9
|
|
|
10
10
|
import claripy
|
|
11
11
|
|
|
12
|
-
from angr import sim_options
|
|
13
12
|
from angr.analyses import Analysis, AnalysesHub
|
|
14
|
-
from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError
|
|
13
|
+
from angr.errors import SimMemoryMissingError, AngrCallableMultistateError, AngrCallableError, AngrAnalysisError
|
|
15
14
|
from angr.calling_conventions import SimRegArg, default_cc
|
|
16
15
|
from angr.state_plugins.sim_action import SimActionData
|
|
16
|
+
from angr.sim_options import ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY, TRACK_MEMORY_ACTIONS
|
|
17
17
|
from angr.sim_type import SimTypeFunction, SimTypeBottom, SimTypePointer
|
|
18
18
|
from angr.analyses.reaching_definitions import ObservationPointType
|
|
19
19
|
from angr.utils.graph import GraphUtils
|
|
@@ -23,12 +23,23 @@ from .irsb_reg_collector import IRSBRegisterCollector
|
|
|
23
23
|
_l = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
STEP_LIMIT_FIND = 500
|
|
27
|
+
STEP_LIMIT_ANALYSIS = 5000
|
|
28
|
+
|
|
29
|
+
|
|
26
30
|
class StringDeobFuncDescriptor:
|
|
31
|
+
"""
|
|
32
|
+
Describes a string deobfuscation function.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
string_input_arg_idx: int
|
|
36
|
+
string_output_arg_idx: int
|
|
37
|
+
string_length_arg_idx: int | None
|
|
38
|
+
string_null_terminating: bool | None
|
|
39
|
+
|
|
27
40
|
def __init__(self):
|
|
28
|
-
self.string_input_arg_idx = None
|
|
29
|
-
self.string_output_arg_idx = None
|
|
30
41
|
self.string_length_arg_idx = None
|
|
31
|
-
self.string_null_terminating
|
|
42
|
+
self.string_null_terminating = None
|
|
32
43
|
|
|
33
44
|
|
|
34
45
|
class StringObfuscationFinder(Analysis):
|
|
@@ -89,6 +100,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
89
100
|
# Type 1 string deobfuscation functions will decrypt each string once and for good.
|
|
90
101
|
|
|
91
102
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
103
|
+
if cfg is None:
|
|
104
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
105
|
+
|
|
92
106
|
arch = self.project.arch
|
|
93
107
|
|
|
94
108
|
type1_candidates: list[tuple[int, StringDeobFuncDescriptor]] = []
|
|
@@ -100,6 +114,10 @@ class StringObfuscationFinder(Analysis):
|
|
|
100
114
|
if func.prototype is None or len(func.prototype.args) < 1:
|
|
101
115
|
continue
|
|
102
116
|
|
|
117
|
+
if len(func.arguments) != len(func.prototype.args):
|
|
118
|
+
# function argument locations and function prototype arguments do not match
|
|
119
|
+
continue
|
|
120
|
+
|
|
103
121
|
if self.project.kb.functions.callgraph.out_degree[func.addr] != 0:
|
|
104
122
|
continue
|
|
105
123
|
|
|
@@ -123,14 +141,22 @@ class StringObfuscationFinder(Analysis):
|
|
|
123
141
|
dec = self.project.analyses.Decompiler(func, cfg=cfg)
|
|
124
142
|
except Exception: # pylint:disable=broad-exception-caught
|
|
125
143
|
continue
|
|
126
|
-
if
|
|
144
|
+
if (
|
|
145
|
+
dec.codegen is None
|
|
146
|
+
or not dec.codegen.text
|
|
147
|
+
or not self._like_type1_deobfuscation_function(dec.codegen.text)
|
|
148
|
+
):
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
func_node = cfg.get_any_node(func.addr)
|
|
152
|
+
if func_node is None:
|
|
127
153
|
continue
|
|
128
154
|
|
|
129
155
|
args_list = []
|
|
130
156
|
for caller in callers:
|
|
131
157
|
callsite_nodes = [
|
|
132
158
|
pred
|
|
133
|
-
for pred in cfg.get_predecessors(
|
|
159
|
+
for pred in cfg.get_predecessors(func_node)
|
|
134
160
|
if pred.function_address == caller and pred.instruction_addrs
|
|
135
161
|
]
|
|
136
162
|
observation_points = []
|
|
@@ -148,15 +174,21 @@ class StringObfuscationFinder(Analysis):
|
|
|
148
174
|
callsite_node.instruction_addrs[-1],
|
|
149
175
|
ObservationPointType.OP_BEFORE,
|
|
150
176
|
)
|
|
177
|
+
if observ is None:
|
|
178
|
+
continue
|
|
151
179
|
# load values for each function argument
|
|
152
180
|
args: list[tuple[int, Any]] = []
|
|
153
181
|
for arg_idx, func_arg in enumerate(func.arguments):
|
|
154
182
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
155
183
|
# FIXME: stack-passing arguments are used
|
|
184
|
+
real_arg = func.prototype.args[arg_idx]
|
|
156
185
|
if isinstance(func_arg, SimRegArg):
|
|
157
186
|
reg_offset, reg_size = arch.registers[func_arg.reg_name]
|
|
187
|
+
arg_size = (
|
|
188
|
+
real_arg.size if real_arg.size is not None else reg_size
|
|
189
|
+
) // self.project.arch.byte_width
|
|
158
190
|
try:
|
|
159
|
-
mv = observ.registers.load(reg_offset, size=
|
|
191
|
+
mv = observ.registers.load(reg_offset, size=arg_size)
|
|
160
192
|
except SimMemoryMissingError:
|
|
161
193
|
args.append((arg_idx, claripy.BVV(0xDEADBEEF, self.project.arch.bits)))
|
|
162
194
|
continue
|
|
@@ -185,7 +217,15 @@ class StringObfuscationFinder(Analysis):
|
|
|
185
217
|
# now that we have good arguments, let's test the function!
|
|
186
218
|
for args in args_list:
|
|
187
219
|
func_call = self.project.factory.callable(
|
|
188
|
-
func.addr,
|
|
220
|
+
func.addr,
|
|
221
|
+
concrete_only=True,
|
|
222
|
+
cc=func.calling_convention,
|
|
223
|
+
prototype=func.prototype,
|
|
224
|
+
add_options={
|
|
225
|
+
ZERO_FILL_UNCONSTRAINED_MEMORY,
|
|
226
|
+
ZERO_FILL_UNCONSTRAINED_REGISTERS,
|
|
227
|
+
},
|
|
228
|
+
step_limit=STEP_LIMIT_FIND,
|
|
189
229
|
)
|
|
190
230
|
|
|
191
231
|
# before calling the function, let's record the crime scene
|
|
@@ -202,6 +242,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
202
242
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
203
243
|
continue
|
|
204
244
|
|
|
245
|
+
if func_call.result_state is None:
|
|
246
|
+
continue
|
|
247
|
+
|
|
205
248
|
# let's see what this amazing function has done
|
|
206
249
|
# TODO: Support cases where input and output are using different function arguments
|
|
207
250
|
for arg_idx, addr, old_value in values:
|
|
@@ -240,6 +283,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
240
283
|
|
|
241
284
|
arch = self.project.arch
|
|
242
285
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
286
|
+
if cfg is None:
|
|
287
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
288
|
+
|
|
243
289
|
func = self.kb.functions.get_by_addr(func_addr)
|
|
244
290
|
func_node = cfg.get_any_node(func_addr)
|
|
245
291
|
assert func_node is not None
|
|
@@ -260,14 +306,20 @@ class StringObfuscationFinder(Analysis):
|
|
|
260
306
|
callsite_node.instruction_addrs[-1],
|
|
261
307
|
ObservationPointType.OP_BEFORE,
|
|
262
308
|
)
|
|
309
|
+
if observ is None:
|
|
310
|
+
continue
|
|
263
311
|
args = []
|
|
264
|
-
|
|
312
|
+
assert func.prototype is not None and len(func.arguments) == len(func.prototype.args)
|
|
313
|
+
for func_arg, real_arg in zip(func.arguments, func.prototype.args):
|
|
265
314
|
# FIXME: We are ignoring all non-register function arguments until we see a test case where
|
|
266
315
|
# FIXME: stack-passing arguments are used
|
|
267
316
|
if isinstance(func_arg, SimRegArg):
|
|
268
317
|
reg_offset, reg_size = arch.registers[func_arg.reg_name]
|
|
318
|
+
arg_size = (
|
|
319
|
+
real_arg.size if real_arg.size is not None else reg_size
|
|
320
|
+
) // self.project.arch.byte_width
|
|
269
321
|
try:
|
|
270
|
-
mv = observ.registers.load(reg_offset, size=
|
|
322
|
+
mv = observ.registers.load(reg_offset, size=arg_size)
|
|
271
323
|
except SimMemoryMissingError:
|
|
272
324
|
args.append(claripy.BVV(0xDEADBEEF, self.project.arch.bits))
|
|
273
325
|
continue
|
|
@@ -286,7 +338,12 @@ class StringObfuscationFinder(Analysis):
|
|
|
286
338
|
|
|
287
339
|
# call the function
|
|
288
340
|
func_call = self.project.factory.callable(
|
|
289
|
-
func.addr,
|
|
341
|
+
func.addr,
|
|
342
|
+
concrete_only=True,
|
|
343
|
+
cc=func.calling_convention,
|
|
344
|
+
prototype=func.prototype,
|
|
345
|
+
add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
346
|
+
step_limit=STEP_LIMIT_ANALYSIS,
|
|
290
347
|
)
|
|
291
348
|
try:
|
|
292
349
|
func_call(*args)
|
|
@@ -303,6 +360,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
303
360
|
)
|
|
304
361
|
continue
|
|
305
362
|
|
|
363
|
+
if func_call.result_state is None:
|
|
364
|
+
continue
|
|
365
|
+
|
|
306
366
|
# dump the decrypted string!
|
|
307
367
|
output_addr = args[desc.string_output_arg_idx]
|
|
308
368
|
length = args[desc.string_length_arg_idx].concrete_value if desc.string_length_arg_idx is not None else 256
|
|
@@ -322,6 +382,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
322
382
|
xref_set = xrefs.get_xrefs_by_dst(str_addr)
|
|
323
383
|
block_addrs = {xref.block_addr for xref in xref_set}
|
|
324
384
|
for block_addr in block_addrs:
|
|
385
|
+
if block_addr is None:
|
|
386
|
+
continue
|
|
325
387
|
node = cfg.get_any_node(block_addr)
|
|
326
388
|
if node is not None:
|
|
327
389
|
callees = list(self.kb.functions.callgraph.successors(node.function_address))
|
|
@@ -340,6 +402,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
340
402
|
# Type 2 string deobfuscation functions will decrypt each string once and for good.
|
|
341
403
|
|
|
342
404
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
405
|
+
if cfg is None:
|
|
406
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
343
407
|
|
|
344
408
|
type2_candidates: list[tuple[int, StringDeobFuncDescriptor, list[tuple[int, int, bytes]]]] = []
|
|
345
409
|
|
|
@@ -374,7 +438,11 @@ class StringObfuscationFinder(Analysis):
|
|
|
374
438
|
dec = self.project.analyses.Decompiler(func, cfg=cfg, expr_collapse_depth=64)
|
|
375
439
|
except Exception: # pylint:disable=broad-exception-caught
|
|
376
440
|
continue
|
|
377
|
-
if
|
|
441
|
+
if (
|
|
442
|
+
dec.codegen is None
|
|
443
|
+
or not dec.codegen.text
|
|
444
|
+
or not self._like_type2_deobfuscation_function(dec.codegen.text)
|
|
445
|
+
):
|
|
378
446
|
continue
|
|
379
447
|
|
|
380
448
|
desc = StringDeobFuncDescriptor()
|
|
@@ -384,7 +452,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
384
452
|
concrete_only=True,
|
|
385
453
|
cc=func.calling_convention,
|
|
386
454
|
prototype=func.prototype,
|
|
387
|
-
add_options={
|
|
455
|
+
add_options={TRACK_MEMORY_ACTIONS, ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
456
|
+
step_limit=STEP_LIMIT_FIND,
|
|
388
457
|
)
|
|
389
458
|
|
|
390
459
|
try:
|
|
@@ -392,6 +461,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
392
461
|
except (AngrCallableMultistateError, AngrCallableError):
|
|
393
462
|
continue
|
|
394
463
|
|
|
464
|
+
if func_call.result_state is None:
|
|
465
|
+
continue
|
|
466
|
+
|
|
395
467
|
# where are the reads and writes?
|
|
396
468
|
all_global_reads = []
|
|
397
469
|
all_global_writes = []
|
|
@@ -399,7 +471,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
399
471
|
if not isinstance(action, SimActionData):
|
|
400
472
|
continue
|
|
401
473
|
if not action.actual_addrs:
|
|
402
|
-
if not action.addr.ast.concrete:
|
|
474
|
+
if action.addr is None or not action.addr.ast.concrete:
|
|
403
475
|
continue
|
|
404
476
|
actual_addrs = [action.addr.ast.concrete_value]
|
|
405
477
|
else:
|
|
@@ -469,6 +541,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
469
541
|
"""
|
|
470
542
|
|
|
471
543
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
544
|
+
if cfg is None:
|
|
545
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
472
546
|
|
|
473
547
|
# for each string table address, we find its string loader function
|
|
474
548
|
# an obvious candidate function is 0x140001b20
|
|
@@ -478,6 +552,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
478
552
|
xref_set = xrefs.get_xrefs_by_dst(table_addr)
|
|
479
553
|
block_addrs = {xref.block_addr for xref in xref_set}
|
|
480
554
|
for block_addr in block_addrs:
|
|
555
|
+
if block_addr is None:
|
|
556
|
+
continue
|
|
481
557
|
node = cfg.get_any_node(block_addr)
|
|
482
558
|
if node is not None:
|
|
483
559
|
callees = list(self.kb.functions.callgraph.successors(node.function_address))
|
|
@@ -496,6 +572,9 @@ class StringObfuscationFinder(Analysis):
|
|
|
496
572
|
# not have a SimProcedure for)
|
|
497
573
|
|
|
498
574
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
575
|
+
if cfg is None:
|
|
576
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
577
|
+
|
|
499
578
|
functions = self.kb.functions
|
|
500
579
|
callgraph_digraph = networkx.DiGraph(functions.callgraph)
|
|
501
580
|
|
|
@@ -554,7 +633,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
554
633
|
except Exception: # pylint:disable=broad-exception-caught
|
|
555
634
|
# catch all exceptions
|
|
556
635
|
continue
|
|
557
|
-
if dec.codegen is None:
|
|
636
|
+
if dec.codegen is None or not dec.codegen.text:
|
|
558
637
|
continue
|
|
559
638
|
if not self._like_type3_deobfuscation_function(dec.codegen.text):
|
|
560
639
|
continue
|
|
@@ -605,6 +684,8 @@ class StringObfuscationFinder(Analysis):
|
|
|
605
684
|
"""
|
|
606
685
|
|
|
607
686
|
cfg = self.kb.cfgs.get_most_accurate()
|
|
687
|
+
if cfg is None:
|
|
688
|
+
raise AngrAnalysisError("StringObfuscationFinder needs a CFG for the analysis")
|
|
608
689
|
|
|
609
690
|
call_sites = cfg.get_predecessors(cfg.get_any_node(func_addr))
|
|
610
691
|
callinsn2content = {}
|
|
@@ -687,7 +768,7 @@ class StringObfuscationFinder(Analysis):
|
|
|
687
768
|
# execute the block at the call site
|
|
688
769
|
state = self.project.factory.blank_state(
|
|
689
770
|
addr=call_site_addr,
|
|
690
|
-
add_options={
|
|
771
|
+
add_options={ZERO_FILL_UNCONSTRAINED_REGISTERS, ZERO_FILL_UNCONSTRAINED_MEMORY},
|
|
691
772
|
)
|
|
692
773
|
# setup sp and bp, just in case
|
|
693
774
|
state.regs._sp = 0x7FFF0000
|
|
@@ -728,7 +809,13 @@ class StringObfuscationFinder(Analysis):
|
|
|
728
809
|
self.project.arch
|
|
729
810
|
)
|
|
730
811
|
callable_0 = self.project.factory.callable(
|
|
731
|
-
func_addr,
|
|
812
|
+
func_addr,
|
|
813
|
+
concrete_only=True,
|
|
814
|
+
base_state=in_state,
|
|
815
|
+
cc=cc,
|
|
816
|
+
prototype=prototype_0,
|
|
817
|
+
add_options={ZERO_FILL_UNCONSTRAINED_MEMORY, ZERO_FILL_UNCONSTRAINED_REGISTERS},
|
|
818
|
+
step_limit=STEP_LIMIT_ANALYSIS,
|
|
732
819
|
)
|
|
733
820
|
|
|
734
821
|
try:
|
|
@@ -181,7 +181,7 @@ class ForwardAnalysis(Generic[AnalysisState, NodeType, JobType, JobKey]):
|
|
|
181
181
|
"""
|
|
182
182
|
return node
|
|
183
183
|
|
|
184
|
-
def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool, AnalysisState]:
|
|
184
|
+
def _run_on_node(self, node: NodeType, state: AnalysisState) -> tuple[bool | None, AnalysisState]:
|
|
185
185
|
"""
|
|
186
186
|
The analysis routine that runs on each node in the graph.
|
|
187
187
|
|
|
@@ -31,7 +31,7 @@ class ClaripyDataEngineMixin(
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def _vex_make_comparison(
|
|
34
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
|
|
34
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
|
|
35
35
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
36
36
|
@SimEngineLightVEX.binop_handler
|
|
37
37
|
def inner(self, expr):
|
|
@@ -44,7 +44,7 @@ def _vex_make_comparison(
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def _vex_make_vec_comparison(
|
|
47
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool]
|
|
47
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.Bool],
|
|
48
48
|
) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
|
|
49
49
|
@SimEngineLightVEX.binopv_handler
|
|
50
50
|
def inner(self, size, count, expr):
|
|
@@ -56,7 +56,7 @@ def _vex_make_vec_comparison(
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
def _vex_make_operation(
|
|
59
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
59
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
60
60
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
61
61
|
@SimEngineLightVEX.binop_handler
|
|
62
62
|
def inner(self, expr: Binop):
|
|
@@ -70,7 +70,7 @@ def _vex_make_operation(
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def _vex_make_unary_operation(
|
|
73
|
-
func: Callable[[claripy.ast.BV], claripy.ast.BV]
|
|
73
|
+
func: Callable[[claripy.ast.BV], claripy.ast.BV],
|
|
74
74
|
) -> Callable[[ClaripyDataEngineMixin, Unop], claripy.ast.BV]:
|
|
75
75
|
@SimEngineLightVEX.unop_handler
|
|
76
76
|
def inner(self, expr):
|
|
@@ -84,7 +84,7 @@ def _vex_make_unary_operation(
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def _vex_make_shift_operation(
|
|
87
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
87
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
88
88
|
) -> Callable[[ClaripyDataEngineMixin, Binop], claripy.ast.BV]:
|
|
89
89
|
@_vex_make_operation
|
|
90
90
|
def inner(a, b):
|
|
@@ -99,7 +99,7 @@ def _vex_make_shift_operation(
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def _vex_make_vec_operation(
|
|
102
|
-
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV]
|
|
102
|
+
func: Callable[[claripy.ast.BV, claripy.ast.BV], claripy.ast.BV],
|
|
103
103
|
) -> Callable[[ClaripyDataEngineMixin, int, int, Binop], claripy.ast.BV]:
|
|
104
104
|
@SimEngineLightVEX.binopv_handler
|
|
105
105
|
def inner(self, size, count, expr):
|
|
@@ -15,7 +15,7 @@ from angr.knowledge_plugins.key_definitions.atoms import (
|
|
|
15
15
|
from angr.knowledge_plugins.key_definitions.definition import Definition
|
|
16
16
|
from angr.analyses import register_analysis
|
|
17
17
|
from .reaching_definitions import ReachingDefinitionsAnalysis, ReachingDefinitionsModel
|
|
18
|
-
from .function_handler import FunctionHandler, FunctionCallData
|
|
18
|
+
from .function_handler import FunctionHandler, FunctionCallData, FunctionCallRelationships
|
|
19
19
|
from .rd_state import ReachingDefinitionsState
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
@@ -29,6 +29,7 @@ __all__ = (
|
|
|
29
29
|
"ConstantSrc",
|
|
30
30
|
"Definition",
|
|
31
31
|
"FunctionCallData",
|
|
32
|
+
"FunctionCallRelationships",
|
|
32
33
|
"FunctionHandler",
|
|
33
34
|
"GuardUse",
|
|
34
35
|
"LiveDefinitions",
|
|
@@ -6,7 +6,6 @@ from typing import (
|
|
|
6
6
|
Any,
|
|
7
7
|
)
|
|
8
8
|
from collections.abc import Iterable, Iterator
|
|
9
|
-
from dataclasses import dataclass
|
|
10
9
|
|
|
11
10
|
import networkx
|
|
12
11
|
|
|
@@ -35,16 +34,6 @@ def _is_definition(node):
|
|
|
35
34
|
return isinstance(node, Definition)
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
@dataclass
|
|
39
|
-
class FunctionCallRelationships: # TODO this doesn't belong in this file anymore
|
|
40
|
-
callsite: CodeLocation
|
|
41
|
-
target: int | None
|
|
42
|
-
args_defns: list[set[Definition]]
|
|
43
|
-
other_input_defns: set[Definition]
|
|
44
|
-
ret_defns: set[Definition]
|
|
45
|
-
other_output_defns: set[Definition]
|
|
46
|
-
|
|
47
|
-
|
|
48
37
|
class DepGraph:
|
|
49
38
|
"""
|
|
50
39
|
The representation of a dependency graph: a directed graph, where nodes are definitions, and edges represent uses.
|
|
@@ -84,7 +73,7 @@ class DepGraph:
|
|
|
84
73
|
"""
|
|
85
74
|
self._graph.add_edge(source, destination, **labels)
|
|
86
75
|
|
|
87
|
-
def nodes(self) ->
|
|
76
|
+
def nodes(self) -> Iterator[Definition]:
|
|
88
77
|
return self._graph.nodes()
|
|
89
78
|
|
|
90
79
|
def predecessors(self, node: Definition) -> Iterator[Definition]:
|
|
@@ -12,7 +12,8 @@ from angr.engines.light import SimEngineNostmtVEX, SpOffset
|
|
|
12
12
|
from angr.engines.vex.claripy.datalayer import value as claripy_value
|
|
13
13
|
from angr.errors import SimEngineError, SimMemoryMissingError
|
|
14
14
|
from angr.utils.constants import DEFAULT_STATEMENT
|
|
15
|
-
from angr.knowledge_plugins.key_definitions.
|
|
15
|
+
from angr.knowledge_plugins.key_definitions.definition import Definition
|
|
16
|
+
from angr.knowledge_plugins.key_definitions.live_definitions import LiveDefinitions
|
|
16
17
|
from angr.knowledge_plugins.key_definitions.tag import LocalVariableTag, ParameterTag, Tag
|
|
17
18
|
from angr.knowledge_plugins.key_definitions.atoms import Atom, Register, MemoryLocation, Tmp
|
|
18
19
|
from angr.knowledge_plugins.key_definitions.constants import OP_BEFORE, OP_AFTER
|
|
@@ -78,11 +79,15 @@ class SimEngineRDVEX(
|
|
|
78
79
|
self._set_codeloc()
|
|
79
80
|
if self.block.vex.jumpkind == "Ijk_Call":
|
|
80
81
|
# it has to be a function
|
|
81
|
-
|
|
82
|
+
block_next = self.block.vex.next
|
|
83
|
+
assert isinstance(block_next, pyvex.expr.IRExpr)
|
|
84
|
+
addr = self._expr_bv(block_next)
|
|
82
85
|
self._handle_function(addr)
|
|
83
86
|
elif self.block.vex.jumpkind == "Ijk_Boring":
|
|
84
87
|
# test if the target addr is a function or not
|
|
85
|
-
|
|
88
|
+
block_next = self.block.vex.next
|
|
89
|
+
assert isinstance(block_next, pyvex.expr.IRExpr)
|
|
90
|
+
addr = self._expr_bv(block_next)
|
|
86
91
|
addr_v = addr.one_value()
|
|
87
92
|
if addr_v is not None and addr_v.concrete:
|
|
88
93
|
addr_int = addr_v.concrete_value
|
|
@@ -550,7 +555,7 @@ class SimEngineRDVEX(
|
|
|
550
555
|
return r
|
|
551
556
|
|
|
552
557
|
@unop_handler
|
|
553
|
-
def _handle_unop_Not(self, expr):
|
|
558
|
+
def _handle_unop_Not(self, expr: pyvex.expr.Unop) -> MultiValues:
|
|
554
559
|
arg0 = expr.args[0]
|
|
555
560
|
expr_0 = self._expr_bv(arg0)
|
|
556
561
|
bits = expr.result_size(self.tyenv)
|
|
@@ -563,7 +568,7 @@ class SimEngineRDVEX(
|
|
|
563
568
|
return MultiValues(self.state.top(bits))
|
|
564
569
|
|
|
565
570
|
@unop_handler
|
|
566
|
-
def _handle_unop_Clz(self, expr):
|
|
571
|
+
def _handle_unop_Clz(self, expr: pyvex.expr.Unop) -> MultiValues:
|
|
567
572
|
arg0 = expr.args[0]
|
|
568
573
|
_ = self._expr(arg0)
|
|
569
574
|
bits = expr.result_size(self.tyenv)
|
|
@@ -571,7 +576,7 @@ class SimEngineRDVEX(
|
|
|
571
576
|
return MultiValues(self.state.top(bits))
|
|
572
577
|
|
|
573
578
|
@unop_handler
|
|
574
|
-
def _handle_unop_Ctz(self, expr):
|
|
579
|
+
def _handle_unop_Ctz(self, expr: pyvex.expr.Unop) -> MultiValues:
|
|
575
580
|
arg0 = expr.args[0]
|
|
576
581
|
_ = self._expr(arg0)
|
|
577
582
|
bits = expr.result_size(self.tyenv)
|
|
@@ -582,19 +587,19 @@ class SimEngineRDVEX(
|
|
|
582
587
|
# Binary operation handlers
|
|
583
588
|
#
|
|
584
589
|
@binop_handler
|
|
585
|
-
def _handle_binop_ExpCmpNE64(self, expr):
|
|
590
|
+
def _handle_binop_ExpCmpNE64(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
586
591
|
_, _ = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
587
592
|
bits = expr.result_size(self.tyenv)
|
|
588
593
|
# Need to actually implement this later
|
|
589
594
|
return MultiValues(self.state.top(bits))
|
|
590
595
|
|
|
591
596
|
@binop_handler
|
|
592
|
-
def _handle_binop_16HLto32(self, expr):
|
|
597
|
+
def _handle_binop_16HLto32(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
593
598
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
594
599
|
return expr0.concat(expr1)
|
|
595
600
|
|
|
596
601
|
@binop_handler
|
|
597
|
-
def _handle_binop_Add(self, expr):
|
|
602
|
+
def _handle_binop_Add(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
598
603
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
599
604
|
bits = expr.result_size(self.tyenv)
|
|
600
605
|
|
|
@@ -625,7 +630,7 @@ class SimEngineRDVEX(
|
|
|
625
630
|
return r
|
|
626
631
|
|
|
627
632
|
@binop_handler
|
|
628
|
-
def _handle_binop_Sub(self, expr):
|
|
633
|
+
def _handle_binop_Sub(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
629
634
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
630
635
|
bits = expr.result_size(self.tyenv)
|
|
631
636
|
|
|
@@ -656,7 +661,7 @@ class SimEngineRDVEX(
|
|
|
656
661
|
return r
|
|
657
662
|
|
|
658
663
|
@binop_handler
|
|
659
|
-
def _handle_binop_Mul(self, expr):
|
|
664
|
+
def _handle_binop_Mul(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
660
665
|
expr0, expr1 = self._expr_pair(expr.args[0], expr.args[1])
|
|
661
666
|
bits = expr.result_size(self.tyenv)
|
|
662
667
|
|
|
@@ -687,13 +692,13 @@ class SimEngineRDVEX(
|
|
|
687
692
|
return r
|
|
688
693
|
|
|
689
694
|
@binop_handler
|
|
690
|
-
def _handle_binop_Mull(self, expr):
|
|
695
|
+
def _handle_binop_Mull(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
691
696
|
_, _ = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
692
697
|
bits = expr.result_size(self.tyenv)
|
|
693
698
|
return MultiValues(self.state.top(bits))
|
|
694
699
|
|
|
695
700
|
@binop_handler
|
|
696
|
-
def _handle_binop_Div(self, expr):
|
|
701
|
+
def _handle_binop_Div(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
697
702
|
expr0, expr1 = self._expr_pair(expr.args[0], expr.args[1])
|
|
698
703
|
bits = expr.result_size(self.tyenv)
|
|
699
704
|
|
|
@@ -727,19 +732,20 @@ class SimEngineRDVEX(
|
|
|
727
732
|
return r
|
|
728
733
|
|
|
729
734
|
@binop_handler
|
|
730
|
-
def _handle_binop_DivMod(self, expr):
|
|
735
|
+
def _handle_binop_DivMod(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
731
736
|
_, _ = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
732
737
|
bits = expr.result_size(self.tyenv)
|
|
733
738
|
|
|
734
739
|
return MultiValues(self.state.top(bits))
|
|
735
740
|
|
|
736
|
-
|
|
741
|
+
@binop_handler
|
|
742
|
+
def _handle_Mod(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
737
743
|
_, _ = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
738
744
|
bits = expr.result_size(self.tyenv)
|
|
739
745
|
return MultiValues(self.state.top(bits))
|
|
740
746
|
|
|
741
747
|
@binop_handler
|
|
742
|
-
def _handle_binop_And(self, expr):
|
|
748
|
+
def _handle_binop_And(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
743
749
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
744
750
|
bits = expr.result_size(self.tyenv)
|
|
745
751
|
|
|
@@ -748,9 +754,8 @@ class SimEngineRDVEX(
|
|
|
748
754
|
expr1_v = expr1.one_value()
|
|
749
755
|
|
|
750
756
|
if expr0_v is not None and expr1_v is not None:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
r = MultiValues(expr0_v & expr1_v)
|
|
757
|
+
# bitwise-and two single values together
|
|
758
|
+
r = MultiValues(expr0_v & expr1_v)
|
|
754
759
|
elif expr0_v is None and expr1_v is not None:
|
|
755
760
|
# bitwise-and a single value with a multivalue
|
|
756
761
|
if expr0.count() == 1 and 0 in expr0:
|
|
@@ -771,7 +776,7 @@ class SimEngineRDVEX(
|
|
|
771
776
|
return r
|
|
772
777
|
|
|
773
778
|
@binop_handler
|
|
774
|
-
def _handle_binop_Xor(self, expr):
|
|
779
|
+
def _handle_binop_Xor(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
775
780
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
776
781
|
bits = expr.result_size(self.tyenv)
|
|
777
782
|
|
|
@@ -803,7 +808,7 @@ class SimEngineRDVEX(
|
|
|
803
808
|
return r
|
|
804
809
|
|
|
805
810
|
@binop_handler
|
|
806
|
-
def _handle_binop_Or(self, expr):
|
|
811
|
+
def _handle_binop_Or(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
807
812
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
808
813
|
bits = expr.result_size(self.tyenv)
|
|
809
814
|
|
|
@@ -834,7 +839,7 @@ class SimEngineRDVEX(
|
|
|
834
839
|
return r
|
|
835
840
|
|
|
836
841
|
@binop_handler
|
|
837
|
-
def _handle_binop_Sar(self, expr):
|
|
842
|
+
def _handle_binop_Sar(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
838
843
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
839
844
|
bits = expr.result_size(self.tyenv)
|
|
840
845
|
|
|
@@ -877,7 +882,7 @@ class SimEngineRDVEX(
|
|
|
877
882
|
return r
|
|
878
883
|
|
|
879
884
|
@binop_handler
|
|
880
|
-
def _handle_binop_Shr(self, expr):
|
|
885
|
+
def _handle_binop_Shr(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
881
886
|
expr0, expr1 = self._expr_bv(expr.args[0]), self._expr_bv(expr.args[1])
|
|
882
887
|
bits = expr.result_size(self.tyenv)
|
|
883
888
|
|
|
@@ -918,7 +923,7 @@ class SimEngineRDVEX(
|
|
|
918
923
|
return r
|
|
919
924
|
|
|
920
925
|
@binop_handler
|
|
921
|
-
def _handle_binop_Shl(self, expr):
|
|
926
|
+
def _handle_binop_Shl(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
922
927
|
expr0, expr1 = self._expr(expr.args[0]), self._expr(expr.args[1])
|
|
923
928
|
bits = expr.result_size(self.tyenv)
|
|
924
929
|
|
|
@@ -956,7 +961,7 @@ class SimEngineRDVEX(
|
|
|
956
961
|
return r
|
|
957
962
|
|
|
958
963
|
@binop_handler
|
|
959
|
-
def _handle_binop_CmpEQ(self, expr):
|
|
964
|
+
def _handle_binop_CmpEQ(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
960
965
|
arg0, arg1 = expr.args
|
|
961
966
|
expr_0 = self._expr(arg0)
|
|
962
967
|
expr_1 = self._expr(arg1)
|
|
@@ -974,7 +979,7 @@ class SimEngineRDVEX(
|
|
|
974
979
|
return MultiValues(self.state.top(1))
|
|
975
980
|
|
|
976
981
|
@binop_handler
|
|
977
|
-
def _handle_binop_CmpNE(self, expr):
|
|
982
|
+
def _handle_binop_CmpNE(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
978
983
|
arg0, arg1 = expr.args
|
|
979
984
|
expr_0 = self._expr(arg0)
|
|
980
985
|
expr_1 = self._expr(arg1)
|
|
@@ -989,7 +994,7 @@ class SimEngineRDVEX(
|
|
|
989
994
|
return MultiValues(self.state.top(1))
|
|
990
995
|
|
|
991
996
|
@binop_handler
|
|
992
|
-
def _handle_binop_CmpLT(self, expr):
|
|
997
|
+
def _handle_binop_CmpLT(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
993
998
|
arg0, arg1 = expr.args
|
|
994
999
|
expr_0, expr_1 = self._expr_pair(arg0, arg1)
|
|
995
1000
|
|
|
@@ -1004,7 +1009,7 @@ class SimEngineRDVEX(
|
|
|
1004
1009
|
return MultiValues(self.state.top(1))
|
|
1005
1010
|
|
|
1006
1011
|
@binop_handler
|
|
1007
|
-
def _handle_binop_CmpLE(self, expr):
|
|
1012
|
+
def _handle_binop_CmpLE(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
1008
1013
|
arg0, arg1 = expr.args
|
|
1009
1014
|
expr_0, expr_1 = self._expr_pair(arg0, arg1)
|
|
1010
1015
|
|
|
@@ -1019,7 +1024,7 @@ class SimEngineRDVEX(
|
|
|
1019
1024
|
return MultiValues(self.state.top(1))
|
|
1020
1025
|
|
|
1021
1026
|
@binop_handler
|
|
1022
|
-
def _handle_binop_CmpGT(self, expr):
|
|
1027
|
+
def _handle_binop_CmpGT(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
1023
1028
|
arg0, arg1 = expr.args
|
|
1024
1029
|
expr_0, expr_1 = self._expr_pair(arg0, arg1)
|
|
1025
1030
|
|
|
@@ -1034,7 +1039,7 @@ class SimEngineRDVEX(
|
|
|
1034
1039
|
return MultiValues(self.state.top(1))
|
|
1035
1040
|
|
|
1036
1041
|
@binop_handler
|
|
1037
|
-
def _handle_binop_CmpGE(self, expr):
|
|
1042
|
+
def _handle_binop_CmpGE(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
1038
1043
|
arg0, arg1 = expr.args
|
|
1039
1044
|
expr_0, expr_1 = self._expr_pair(arg0, arg1)
|
|
1040
1045
|
|
|
@@ -1050,7 +1055,7 @@ class SimEngineRDVEX(
|
|
|
1050
1055
|
|
|
1051
1056
|
# ppc only
|
|
1052
1057
|
@binop_handler
|
|
1053
|
-
def _handle_binop_CmpORD(self, expr):
|
|
1058
|
+
def _handle_binop_CmpORD(self, expr: pyvex.expr.Binop) -> MultiValues:
|
|
1054
1059
|
arg0, arg1 = expr.args
|
|
1055
1060
|
expr_0, expr_1 = self._expr_pair(arg0, arg1)
|
|
1056
1061
|
|