angr 9.2.111__py3-none-win_amd64.whl → 9.2.113__py3-none-win_amd64.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/cfg/cfg_base.py +4 -1
- angr/analyses/decompiler/condition_processor.py +9 -2
- angr/analyses/decompiler/optimization_passes/__init__.py +3 -1
- angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +367 -0
- angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
- angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +99 -12
- angr/analyses/decompiler/optimization_passes/optimization_pass.py +79 -9
- angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +21 -0
- angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +111 -9
- angr/analyses/decompiler/redundant_label_remover.py +17 -0
- angr/analyses/decompiler/seq_cf_structure_counter.py +37 -0
- angr/analyses/decompiler/structured_codegen/c.py +4 -5
- angr/analyses/decompiler/structuring/phoenix.py +3 -3
- angr/analyses/reaching_definitions/rd_state.py +2 -0
- angr/analyses/reaching_definitions/reaching_definitions.py +7 -0
- angr/angrdb/serializers/loader.py +91 -7
- angr/calling_conventions.py +11 -9
- angr/knowledge_plugins/key_definitions/live_definitions.py +5 -0
- angr/knowledge_plugins/propagations/states.py +3 -2
- angr/knowledge_plugins/variables/variable_manager.py +1 -1
- angr/lib/angr_native.dll +0 -0
- angr/procedures/stubs/ReturnUnconstrained.py +1 -2
- angr/procedures/stubs/syscall_stub.py +1 -2
- angr/sim_type.py +354 -136
- angr/state_plugins/debug_variables.py +2 -2
- angr/state_plugins/solver.py +5 -13
- angr/storage/memory_mixins/multi_value_merger_mixin.py +13 -3
- angr/utils/orderedset.py +70 -0
- angr/vaults.py +0 -1
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/METADATA +6 -6
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/RECORD +36 -33
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/WHEEL +1 -1
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/LICENSE +0 -0
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/entry_points.txt +0 -0
- {angr-9.2.111.dist-info → angr-9.2.113.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# pylint:disable=unused-argument
|
|
2
|
+
import logging
|
|
2
3
|
from typing import TYPE_CHECKING
|
|
3
4
|
from collections.abc import Generator
|
|
4
5
|
from enum import Enum
|
|
@@ -11,10 +12,13 @@ from angr.analyses.decompiler.condition_processor import ConditionProcessor
|
|
|
11
12
|
from angr.analyses.decompiler.goto_manager import GotoManager
|
|
12
13
|
from angr.analyses.decompiler.structuring import RecursiveStructurer, PhoenixStructurer
|
|
13
14
|
from angr.analyses.decompiler.utils import add_labels
|
|
15
|
+
from angr.analyses.decompiler.seq_cf_structure_counter import ControlFlowStructureCounter
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from angr.knowledge_plugins.functions import Function
|
|
17
19
|
|
|
20
|
+
_l = logging.getLogger(__name__)
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
class MultipleBlocksException(Exception):
|
|
20
24
|
"""
|
|
@@ -274,6 +278,7 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
274
278
|
prevent_new_gotos=True,
|
|
275
279
|
strictly_less_gotos=False,
|
|
276
280
|
recover_structure_fails=True,
|
|
281
|
+
must_improve_rel_quality=True,
|
|
277
282
|
max_opt_iters=1,
|
|
278
283
|
simplify_ail=True,
|
|
279
284
|
require_gotos=True,
|
|
@@ -286,10 +291,15 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
286
291
|
self._max_opt_iters = max_opt_iters
|
|
287
292
|
self._simplify_ail = simplify_ail
|
|
288
293
|
self._require_gotos = require_gotos
|
|
294
|
+
self._must_improve_rel_quality = must_improve_rel_quality
|
|
289
295
|
|
|
290
296
|
self._goto_manager: GotoManager | None = None
|
|
291
297
|
self._prev_graph: networkx.DiGraph | None = None
|
|
292
298
|
|
|
299
|
+
# relative quality metrics (excludes gotos)
|
|
300
|
+
self._initial_structure_counter = None
|
|
301
|
+
self._current_structure_counter = None
|
|
302
|
+
|
|
293
303
|
def _analyze(self, cache=None) -> bool:
|
|
294
304
|
raise NotImplementedError()
|
|
295
305
|
|
|
@@ -297,7 +307,7 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
297
307
|
"""
|
|
298
308
|
Wrapper for _analyze() that verifies the graph is structurable before and after the optimization.
|
|
299
309
|
"""
|
|
300
|
-
if not self._graph_is_structurable(self._graph):
|
|
310
|
+
if not self._graph_is_structurable(self._graph, initial=True):
|
|
301
311
|
return
|
|
302
312
|
|
|
303
313
|
initial_gotos = self._goto_manager.gotos.copy()
|
|
@@ -340,6 +350,10 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
340
350
|
self.out_graph = None
|
|
341
351
|
return
|
|
342
352
|
|
|
353
|
+
if self._must_improve_rel_quality and not self._improves_relative_quality():
|
|
354
|
+
self.out_graph = None
|
|
355
|
+
return
|
|
356
|
+
|
|
343
357
|
def _fixed_point_analyze(self, cache=None):
|
|
344
358
|
for _ in range(self._max_opt_iters):
|
|
345
359
|
if self._require_gotos and not self._goto_manager.gotos:
|
|
@@ -359,7 +373,7 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
359
373
|
self.out_graph = self._prev_graph if self._recover_structure_fails else None
|
|
360
374
|
break
|
|
361
375
|
|
|
362
|
-
def _graph_is_structurable(self, graph, readd_labels=False) -> bool:
|
|
376
|
+
def _graph_is_structurable(self, graph, readd_labels=False, initial=False) -> bool:
|
|
363
377
|
"""
|
|
364
378
|
Checks weather the input graph is structurable under the Phoenix schema-matching structuring algorithm.
|
|
365
379
|
As a side effect, this will also update the region identifier and goto manager of this optimization pass.
|
|
@@ -380,18 +394,74 @@ class StructuringOptimizationPass(OptimizationPass):
|
|
|
380
394
|
if self._ri is None:
|
|
381
395
|
return False
|
|
382
396
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
397
|
+
# we should try-catch structuring here because we can often pass completely invalid graphs
|
|
398
|
+
# that break the assumptions of the structuring algorithm
|
|
399
|
+
try:
|
|
400
|
+
rs = self.project.analyses[RecursiveStructurer].prep(kb=self.kb)(
|
|
401
|
+
self._ri.region,
|
|
402
|
+
cond_proc=self._ri.cond_proc,
|
|
403
|
+
func=self._func,
|
|
404
|
+
structurer_cls=PhoenixStructurer,
|
|
405
|
+
)
|
|
406
|
+
# pylint:disable=broad-except
|
|
407
|
+
except Exception:
|
|
408
|
+
_l.warning("Internal structuring failed for OptimizationPass on %s", self._func.name)
|
|
409
|
+
rs = None
|
|
410
|
+
|
|
389
411
|
if not rs or not rs.result or not rs.result.nodes or rs.result_incomplete:
|
|
390
412
|
return False
|
|
391
413
|
|
|
392
414
|
rs = self.project.analyses.RegionSimplifier(self._func, rs.result, kb=self.kb, variable_kb=self._variable_kb)
|
|
393
|
-
if not rs or rs.goto_manager is None:
|
|
415
|
+
if not rs or rs.goto_manager is None or rs.result is None:
|
|
394
416
|
return False
|
|
395
417
|
|
|
418
|
+
self._analyze_simplified_region(rs.result, initial=initial)
|
|
396
419
|
self._goto_manager = rs.goto_manager
|
|
397
420
|
return True
|
|
421
|
+
|
|
422
|
+
# pylint:disable=no-self-use
|
|
423
|
+
def _analyze_simplified_region(self, region, initial=False):
|
|
424
|
+
"""
|
|
425
|
+
Analyze the simplified regions after a successful structuring pass.
|
|
426
|
+
This should be overridden by the subclass if it needs to do anything with the simplified regions for making
|
|
427
|
+
optimizations decisions.
|
|
428
|
+
"""
|
|
429
|
+
if region is None:
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
# record quality metrics
|
|
433
|
+
if self._must_improve_rel_quality:
|
|
434
|
+
if initial:
|
|
435
|
+
self._initial_structure_counter = ControlFlowStructureCounter(region)
|
|
436
|
+
else:
|
|
437
|
+
self._current_structure_counter = ControlFlowStructureCounter(region)
|
|
438
|
+
|
|
439
|
+
def _improves_relative_quality(self) -> bool:
|
|
440
|
+
"""
|
|
441
|
+
Checks if the new structured output improves (or maintains) the relative quality of the control flow structures
|
|
442
|
+
present in the function.
|
|
443
|
+
|
|
444
|
+
For now, this only involves loops
|
|
445
|
+
"""
|
|
446
|
+
if self._initial_structure_counter is None or self._current_structure_counter is None:
|
|
447
|
+
_l.warning("Relative quality check failed due to missing structure counters")
|
|
448
|
+
return True
|
|
449
|
+
|
|
450
|
+
prev_wloops = self._initial_structure_counter.while_loops
|
|
451
|
+
curr_wloops = self._current_structure_counter.while_loops
|
|
452
|
+
prev_dloops = self._initial_structure_counter.do_while_loops
|
|
453
|
+
curr_dloops = self._current_structure_counter.do_while_loops
|
|
454
|
+
prev_floops = self._initial_structure_counter.for_loops
|
|
455
|
+
curr_floops = self._current_structure_counter.for_loops
|
|
456
|
+
total_prev_loops = prev_wloops + prev_dloops + prev_floops
|
|
457
|
+
total_curr_loops = curr_wloops + curr_dloops + curr_floops
|
|
458
|
+
|
|
459
|
+
# Sometimes, if we mess up structuring you can easily tell because we traded "good" loops for "bad" loops.
|
|
460
|
+
# Generally, loops are ordered good -> bad as follows: for, while, do-while.
|
|
461
|
+
# Note: this check is only for _trading_, meaning the total number of loops must be the same.
|
|
462
|
+
#
|
|
463
|
+
# 1. We traded to remove a for-loop
|
|
464
|
+
if curr_floops < prev_floops and total_curr_loops == total_prev_loops:
|
|
465
|
+
return False
|
|
466
|
+
|
|
467
|
+
return True
|
|
@@ -38,6 +38,7 @@ class ReturnDuplicatorBase:
|
|
|
38
38
|
self.node_idx = count(start=node_idx_start)
|
|
39
39
|
self._max_calls_in_region = max_calls_in_regions
|
|
40
40
|
self._minimize_copies_for_regions = minimize_copies_for_regions
|
|
41
|
+
self._supergraph = None
|
|
41
42
|
|
|
42
43
|
# this should also be set by the optimization passes initer
|
|
43
44
|
self._func = func
|
|
@@ -71,6 +72,8 @@ class ReturnDuplicatorBase:
|
|
|
71
72
|
# for connected in_edges that form a region
|
|
72
73
|
endnode_regions = self._copy_connected_edge_components(endnode_regions, graph)
|
|
73
74
|
|
|
75
|
+
# refresh the supergraph
|
|
76
|
+
self._supergraph = to_ail_supergraph(graph)
|
|
74
77
|
for region_head, (in_edges, region) in endnode_regions.items():
|
|
75
78
|
is_single_const_ret_region = self._is_simple_return_graph(region)
|
|
76
79
|
for in_edge in in_edges:
|
|
@@ -150,6 +153,7 @@ class ReturnDuplicatorBase:
|
|
|
150
153
|
else:
|
|
151
154
|
node_copy = copy.deepcopy(node)
|
|
152
155
|
node_copy.idx = next(self.node_idx)
|
|
156
|
+
self._fix_copied_node_labels(node_copy)
|
|
153
157
|
copies[node] = node_copy
|
|
154
158
|
|
|
155
159
|
# modify Jump.target_idx and ConditionalJump.{true,false}_target_idx accordingly
|
|
@@ -446,3 +450,20 @@ class ReturnDuplicatorBase:
|
|
|
446
450
|
all_region_block_sets = {}
|
|
447
451
|
_unpack_every_region(top_region, all_region_block_sets)
|
|
448
452
|
return all_region_block_sets
|
|
453
|
+
|
|
454
|
+
@staticmethod
|
|
455
|
+
def _fix_copied_node_labels(block: Block):
|
|
456
|
+
for i in range(len(block.statements)): # pylint:disable=consider-using-enumerate
|
|
457
|
+
stmt = block.statements[i]
|
|
458
|
+
if isinstance(stmt, Label):
|
|
459
|
+
# fix the default name by suffixing it with the new block ID
|
|
460
|
+
new_name = stmt.name if stmt.name else f"Label_{stmt.ins_addr:x}"
|
|
461
|
+
if stmt.block_idx is not None:
|
|
462
|
+
suffix = f"__{stmt.block_idx}"
|
|
463
|
+
if new_name.endswith(suffix):
|
|
464
|
+
new_name = new_name[: -len(suffix)]
|
|
465
|
+
else:
|
|
466
|
+
new_name = stmt.name
|
|
467
|
+
new_name += f"__{block.idx}"
|
|
468
|
+
|
|
469
|
+
block.statements[i] = Label(stmt.idx, new_name, stmt.ins_addr, block_idx=block.idx, **stmt.tags)
|
|
@@ -4,7 +4,7 @@ import inspect
|
|
|
4
4
|
import networkx
|
|
5
5
|
|
|
6
6
|
from ailment import Block
|
|
7
|
-
from ailment.statement import ConditionalJump
|
|
7
|
+
from ailment.statement import ConditionalJump, Label
|
|
8
8
|
|
|
9
9
|
from .return_duplicator_base import ReturnDuplicatorBase
|
|
10
10
|
from .optimization_pass import StructuringOptimizationPass
|
|
@@ -71,23 +71,29 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
|
|
|
71
71
|
return ReturnDuplicatorBase._check(self)
|
|
72
72
|
|
|
73
73
|
def _should_duplicate_dst(self, src, dst, graph, dst_is_const_ret=False):
|
|
74
|
-
return self._is_goto_edge(src, dst, graph=graph
|
|
74
|
+
return self._is_goto_edge(src, dst, graph=graph)
|
|
75
75
|
|
|
76
76
|
def _is_goto_edge(
|
|
77
77
|
self,
|
|
78
78
|
src: Block,
|
|
79
79
|
dst: Block,
|
|
80
80
|
graph: networkx.DiGraph = None,
|
|
81
|
-
check_for_ifstmts=True,
|
|
82
81
|
max_level_check=1,
|
|
83
82
|
):
|
|
84
83
|
"""
|
|
85
|
-
TODO:
|
|
84
|
+
TODO: Implement a more principled way of checking if an edge is a goto edge with Phoenix's structuring info
|
|
86
85
|
This function only exists because a long-standing bug that sometimes reports the if-stmt addr
|
|
87
|
-
above a goto edge as the goto src.
|
|
88
|
-
see if they are a goto. This needs to include Jump to deal with loops.
|
|
86
|
+
above a goto edge as the goto src.
|
|
89
87
|
"""
|
|
90
|
-
|
|
88
|
+
# Do a simple and fast check first
|
|
89
|
+
is_simple_goto = self._goto_manager.is_goto_edge(src, dst)
|
|
90
|
+
if is_simple_goto:
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
if graph is not None:
|
|
94
|
+
# Special case 1:
|
|
95
|
+
# We need to check for predecessors above the goto and see if they are a goto.
|
|
96
|
+
# This needs to include Jump to deal with loops.
|
|
91
97
|
blocks = [src]
|
|
92
98
|
level_blocks = [src]
|
|
93
99
|
for _ in range(max_level_check):
|
|
@@ -109,8 +115,104 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
|
|
|
109
115
|
|
|
110
116
|
if self._goto_manager.is_goto_edge(block, dst):
|
|
111
117
|
return True
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
|
|
119
|
+
# Special case 2: A "goto edge" that ReturnDuplicator wants to test might be an edge that Phoenix
|
|
120
|
+
# includes in its loop region (during the cyclic refinement). In fact, Phoenix tends to include as many
|
|
121
|
+
# nodes as possible into the loop region, and generate a goto edge (which ends up in the structured code)
|
|
122
|
+
# from `dst` to the loop successor.
|
|
123
|
+
# an example of this is captured by the test case `TestDecompiler.test_stty_recover_mode_ret_dup_region`.
|
|
124
|
+
# until someone (ideally @mahaloz) implements a more principled way of translating "goto statements" that
|
|
125
|
+
# Phoenix generates and "goto edges" that ReturnDuplicator tests, we rely on the following stopgap to
|
|
126
|
+
# handle this case.
|
|
127
|
+
node = dst
|
|
128
|
+
while True:
|
|
129
|
+
succs = list(graph.successors(node))
|
|
130
|
+
if len(succs) != 1:
|
|
131
|
+
break
|
|
132
|
+
succ = succs[0]
|
|
133
|
+
if succ is node:
|
|
134
|
+
# loop!
|
|
135
|
+
break
|
|
136
|
+
succ_preds = list(graph.predecessors(succ))
|
|
137
|
+
if len(succ_preds) != 1:
|
|
138
|
+
break
|
|
139
|
+
if self._goto_manager.is_goto_edge(node, succ):
|
|
140
|
+
return True
|
|
141
|
+
# keep testing the next edge
|
|
142
|
+
node = succ
|
|
143
|
+
|
|
144
|
+
# Special case 3: In Phoenix, regions full of only if-stmts can be collapsed and moved. This causes
|
|
145
|
+
# the goto manager to report gotos that are at the top of the region instead of ones in the middle of it.
|
|
146
|
+
# Because of this, we need to gather all the nodes above the original src and check if any of them
|
|
147
|
+
# go to the destination. Additionally, we need to do this on the supergraph to get rid of
|
|
148
|
+
# goto edges that are removed by Phoenix.
|
|
149
|
+
# This case is observed in the test case `TestDecompiler.test_tail_tail_bytes_ret_dup`.
|
|
150
|
+
if self._supergraph is None:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
super_to_og_nodes = {n: self._supergraph.nodes[n]["original_nodes"] for n in self._supergraph.nodes}
|
|
154
|
+
og_to_super_nodes = {og: super_n for super_n, ogs in super_to_og_nodes.items() for og in ogs}
|
|
155
|
+
super_src = og_to_super_nodes.get(src, None)
|
|
156
|
+
super_dst = og_to_super_nodes.get(dst, None)
|
|
157
|
+
if super_src is None or super_dst is None:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
# collect all nodes which have only an if-stmt in them that are ancestors of super_src
|
|
161
|
+
check_blks = {super_src}
|
|
162
|
+
level_blocks = {super_src}
|
|
163
|
+
for _ in range(10):
|
|
164
|
+
done = False
|
|
165
|
+
if_blks = set()
|
|
166
|
+
for lblock in level_blocks:
|
|
167
|
+
preds = list(self._supergraph.predecessors(lblock))
|
|
168
|
+
for pred in preds:
|
|
169
|
+
only_cond_jump = all(isinstance(s, (ConditionalJump, Label)) for s in pred.statements)
|
|
170
|
+
if only_cond_jump:
|
|
171
|
+
if_blks.add(pred)
|
|
172
|
+
|
|
173
|
+
done = len(if_blks) == 0
|
|
174
|
+
|
|
175
|
+
if done:
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
check_blks |= if_blks
|
|
179
|
+
level_blocks = if_blks
|
|
180
|
+
|
|
181
|
+
# convert all the found if-only super-blocks back into their original blocks
|
|
182
|
+
og_check_blocks = set()
|
|
183
|
+
for blk in check_blks:
|
|
184
|
+
og_check_blocks |= set(super_to_og_nodes[blk])
|
|
185
|
+
|
|
186
|
+
# check if any of the original blocks are gotos to the destination
|
|
187
|
+
goto_hits = 0
|
|
188
|
+
for block in og_check_blocks:
|
|
189
|
+
if self._goto_manager.is_goto_edge(block, dst):
|
|
190
|
+
goto_hits += 1
|
|
191
|
+
|
|
192
|
+
# Although it is good to find a goto in the if-only block region, having more than a single goto
|
|
193
|
+
# existing that goes to the same dst is a bad sign. This can be seen in the the following test:
|
|
194
|
+
# TestDecompiler.test_dd_iread_ret_dup_region
|
|
195
|
+
#
|
|
196
|
+
# It occurs when you have something like:
|
|
197
|
+
# ```
|
|
198
|
+
# if (a || c)
|
|
199
|
+
# goto target;
|
|
200
|
+
# target:
|
|
201
|
+
# return 0;
|
|
202
|
+
# ```
|
|
203
|
+
#
|
|
204
|
+
#
|
|
205
|
+
# This looks like an edge from (a, target) and (c, target) but it is actually a single edge.
|
|
206
|
+
# If you allow both to duplicate you get the following:
|
|
207
|
+
# ```
|
|
208
|
+
# if (a):
|
|
209
|
+
# return
|
|
210
|
+
# if (c):
|
|
211
|
+
# return
|
|
212
|
+
# ```
|
|
213
|
+
# This is not the desired behavior.
|
|
214
|
+
# So we need to check if there is only a single goto that goes to the destination.
|
|
215
|
+
return goto_hits == 1
|
|
114
216
|
|
|
115
217
|
return False
|
|
116
218
|
|
|
@@ -30,6 +30,9 @@ class RedundantLabelRemover:
|
|
|
30
30
|
self._walker0 = SequenceWalker(handlers=handlers0)
|
|
31
31
|
self._walker0.walk(self.root)
|
|
32
32
|
|
|
33
|
+
# update jump targets
|
|
34
|
+
self._update_jump_targets()
|
|
35
|
+
|
|
33
36
|
handlers1 = {
|
|
34
37
|
ailment.Block: self._handle_Block,
|
|
35
38
|
}
|
|
@@ -37,6 +40,20 @@ class RedundantLabelRemover:
|
|
|
37
40
|
self._walker1.walk(self.root)
|
|
38
41
|
self.result = self.root
|
|
39
42
|
|
|
43
|
+
def _update_jump_targets(self) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Update self._jump_targets after the first pass fills in self._new_jump_target.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
if self._new_jump_target:
|
|
49
|
+
jump_targets = set()
|
|
50
|
+
for jt in self._jump_targets:
|
|
51
|
+
if jt in self._new_jump_target:
|
|
52
|
+
jump_targets.add(self._new_jump_target[jt])
|
|
53
|
+
else:
|
|
54
|
+
jump_targets.add(jt)
|
|
55
|
+
self._jump_targets = jump_targets
|
|
56
|
+
|
|
40
57
|
#
|
|
41
58
|
# Handlers
|
|
42
59
|
#
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from angr.analyses.decompiler.sequence_walker import SequenceWalker
|
|
2
|
+
from angr.analyses.decompiler.structuring.structurer_nodes import SwitchCaseNode, LoopNode
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ControlFlowStructureCounter(SequenceWalker):
|
|
6
|
+
"""
|
|
7
|
+
Counts the number of different types of control flow structures found in a sequence of nodes.
|
|
8
|
+
This should be used after the sequence has been simplified.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, node):
|
|
12
|
+
handlers = {
|
|
13
|
+
LoopNode: self._handle_Loop,
|
|
14
|
+
}
|
|
15
|
+
super().__init__(handlers)
|
|
16
|
+
|
|
17
|
+
self.while_loops = 0
|
|
18
|
+
self.do_while_loops = 0
|
|
19
|
+
self.for_loops = 0
|
|
20
|
+
|
|
21
|
+
self.walk(node)
|
|
22
|
+
|
|
23
|
+
def _handle_Loop(self, node: LoopNode, **kwargs):
|
|
24
|
+
if node.sort == "while":
|
|
25
|
+
self.while_loops += 1
|
|
26
|
+
elif node.sort == "do-while":
|
|
27
|
+
self.do_while_loops += 1
|
|
28
|
+
elif node.sort == "for":
|
|
29
|
+
self.for_loops += 1
|
|
30
|
+
|
|
31
|
+
return super()._handle_Loop(node, **kwargs)
|
|
32
|
+
|
|
33
|
+
def _handle_Condition(self, node, parent=None, **kwargs):
|
|
34
|
+
return super()._handle_Condition(node, parent=parent, **kwargs)
|
|
35
|
+
|
|
36
|
+
def _handle_SwitchCase(self, node: SwitchCaseNode, parent=None, **kwargs):
|
|
37
|
+
return super()._handle_SwitchCase(node, parent=parent, **kwargs)
|
|
@@ -2769,9 +2769,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
|
|
|
2769
2769
|
if offset == 0:
|
|
2770
2770
|
data_type = renegotiate_type(data_type, base_type)
|
|
2771
2771
|
if base_type == data_type or (
|
|
2772
|
-
not
|
|
2773
|
-
and not isinstance(data_type, SimTypeBottom)
|
|
2774
|
-
and base_type.size < data_type.size
|
|
2772
|
+
base_type.size is not None and data_type.size is not None and base_type.size < data_type.size
|
|
2775
2773
|
):
|
|
2776
2774
|
# case 1: we're done because we found it
|
|
2777
2775
|
# case 2: we're done because we can never find it and we might as well stop early
|
|
@@ -2784,7 +2782,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
|
|
|
2784
2782
|
return _force_type_cast(base_type, data_type, expr)
|
|
2785
2783
|
return CUnaryOp("Dereference", expr, codegen=self)
|
|
2786
2784
|
|
|
2787
|
-
if
|
|
2785
|
+
if base_type.size is None:
|
|
2788
2786
|
stride = 1
|
|
2789
2787
|
else:
|
|
2790
2788
|
stride = base_type.size // self.project.arch.byte_width or 1
|
|
@@ -2968,7 +2966,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
|
|
|
2968
2966
|
kernel_type = unpack_typeref(unpack_pointer(kernel.type))
|
|
2969
2967
|
assert kernel_type
|
|
2970
2968
|
|
|
2971
|
-
if
|
|
2969
|
+
if kernel_type.size is None:
|
|
2972
2970
|
return bail_out()
|
|
2973
2971
|
kernel_stride = kernel_type.size // self.project.arch.byte_width
|
|
2974
2972
|
|
|
@@ -3699,6 +3697,7 @@ class MakeTypecastsImplicit(CStructuredCodeWalker):
|
|
|
3699
3697
|
and isinstance(intermediate_ty, (SimTypeChar, SimTypeInt, SimTypeNum))
|
|
3700
3698
|
and isinstance(start_ty, (SimTypeChar, SimTypeInt, SimTypeNum))
|
|
3701
3699
|
):
|
|
3700
|
+
assert dst_ty.size and start_ty.size and intermediate_ty.size
|
|
3702
3701
|
if dst_ty.size <= start_ty.size and dst_ty.size <= intermediate_ty.size:
|
|
3703
3702
|
# this is a down- or neutral-cast with an intermediate step that doesn't matter
|
|
3704
3703
|
result = child.expr
|
|
@@ -719,7 +719,7 @@ class PhoenixStructurer(StructurerBase):
|
|
|
719
719
|
break_stmt = Jump(
|
|
720
720
|
None,
|
|
721
721
|
Const(None, None, successor.addr, self.project.arch.bits),
|
|
722
|
-
None,
|
|
722
|
+
target_idx=successor.idx if isinstance(successor, Block) else None,
|
|
723
723
|
ins_addr=last_src_stmt.ins_addr,
|
|
724
724
|
)
|
|
725
725
|
break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
|
|
@@ -727,7 +727,7 @@ class PhoenixStructurer(StructurerBase):
|
|
|
727
727
|
break_stmt = Jump(
|
|
728
728
|
None,
|
|
729
729
|
Const(None, None, successor.addr, self.project.arch.bits),
|
|
730
|
-
None,
|
|
730
|
+
target_idx=successor.idx if isinstance(successor, Block) else None,
|
|
731
731
|
ins_addr=last_src_stmt.ins_addr,
|
|
732
732
|
)
|
|
733
733
|
break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
|
|
@@ -744,7 +744,7 @@ class PhoenixStructurer(StructurerBase):
|
|
|
744
744
|
break_stmt = Jump(
|
|
745
745
|
None,
|
|
746
746
|
Const(None, None, successor.addr, self.project.arch.bits),
|
|
747
|
-
None,
|
|
747
|
+
target_idx=successor.idx if isinstance(successor, Block) else None,
|
|
748
748
|
ins_addr=last_src_stmt.ins_addr,
|
|
749
749
|
)
|
|
750
750
|
break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
|
|
@@ -93,6 +93,7 @@ class ReachingDefinitionsState:
|
|
|
93
93
|
all_definitions: set[Definition] | None = None,
|
|
94
94
|
initializer: Optional["RDAStateInitializer"] = None,
|
|
95
95
|
element_limit: int = 5,
|
|
96
|
+
merge_into_tops: bool = True,
|
|
96
97
|
):
|
|
97
98
|
# handy short-hands
|
|
98
99
|
self.codeloc = codeloc
|
|
@@ -130,6 +131,7 @@ class ReachingDefinitionsState:
|
|
|
130
131
|
track_tmps=self._track_tmps,
|
|
131
132
|
canonical_size=canonical_size,
|
|
132
133
|
element_limit=element_limit,
|
|
134
|
+
merge_into_tops=merge_into_tops,
|
|
133
135
|
)
|
|
134
136
|
if self.analysis is not None:
|
|
135
137
|
self.live_definitions.project = self.analysis.project
|
|
@@ -76,6 +76,7 @@ class ReachingDefinitionsAnalysis(
|
|
|
76
76
|
track_liveness: bool = True,
|
|
77
77
|
func_addr: int | None = None,
|
|
78
78
|
element_limit: int = 5,
|
|
79
|
+
merge_into_tops: bool = True,
|
|
79
80
|
):
|
|
80
81
|
"""
|
|
81
82
|
:param subject: The subject of the analysis: a function, or a single basic block
|
|
@@ -110,6 +111,10 @@ class ReachingDefinitionsAnalysis(
|
|
|
110
111
|
:param track_liveness: Whether to track liveness information. This can consume
|
|
111
112
|
sizeable amounts of RAM on large functions. (e.g. ~15GB for a function
|
|
112
113
|
with 4k nodes)
|
|
114
|
+
:param merge_into_tops: Merge known values into TOP if TOP is present.
|
|
115
|
+
If True: {TOP} V {0xabc} = {TOP}
|
|
116
|
+
If False: {TOP} V {0xabc} = {TOP, 0xabc}
|
|
117
|
+
|
|
113
118
|
|
|
114
119
|
"""
|
|
115
120
|
|
|
@@ -134,6 +139,7 @@ class ReachingDefinitionsAnalysis(
|
|
|
134
139
|
self._use_callee_saved_regs_at_return = use_callee_saved_regs_at_return
|
|
135
140
|
self._func_addr = func_addr
|
|
136
141
|
self._element_limit = element_limit
|
|
142
|
+
self._merge_into_tops = merge_into_tops
|
|
137
143
|
|
|
138
144
|
if dep_graph is None or dep_graph is False:
|
|
139
145
|
self._dep_graph = None
|
|
@@ -473,6 +479,7 @@ class ReachingDefinitionsAnalysis(
|
|
|
473
479
|
canonical_size=self._canonical_size,
|
|
474
480
|
initializer=self._state_initializer,
|
|
475
481
|
element_limit=self._element_limit,
|
|
482
|
+
merge_into_tops=self._merge_into_tops,
|
|
476
483
|
)
|
|
477
484
|
|
|
478
485
|
# pylint: disable=no-self-use,arguments-differ
|