angr 9.2.150__py3-none-manylinux2014_aarch64.whl → 9.2.153__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.

Files changed (33) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +17 -9
  3. angr/analyses/cfg/cfg_base.py +1 -1
  4. angr/analyses/cfg/cfg_fast.py +39 -0
  5. angr/analyses/decompiler/ail_simplifier.py +0 -1
  6. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +39 -0
  7. angr/analyses/decompiler/clinic.py +118 -2
  8. angr/analyses/decompiler/dephication/rewriting_engine.py +38 -1
  9. angr/analyses/decompiler/optimization_passes/condition_constprop.py +6 -0
  10. angr/analyses/decompiler/optimization_passes/engine_base.py +5 -0
  11. angr/analyses/decompiler/peephole_optimizations/__init__.py +2 -0
  12. angr/analyses/decompiler/peephole_optimizations/cas_intrinsics.py +115 -0
  13. angr/analyses/decompiler/region_identifier.py +171 -119
  14. angr/analyses/decompiler/ssailification/rewriting_engine.py +37 -1
  15. angr/analyses/decompiler/ssailification/traversal_engine.py +10 -1
  16. angr/analyses/reaching_definitions/engine_ail.py +20 -0
  17. angr/analyses/s_propagator.py +28 -0
  18. angr/analyses/smc.py +3 -1
  19. angr/analyses/stack_pointer_tracker.py +2 -1
  20. angr/analyses/typehoon/simple_solver.py +143 -81
  21. angr/analyses/typehoon/typehoon.py +2 -1
  22. angr/analyses/variable_recovery/engine_ail.py +9 -0
  23. angr/engines/light/engine.py +7 -0
  24. angr/knowledge_plugins/functions/function.py +10 -4
  25. angr/storage/memory_mixins/clouseau_mixin.py +7 -1
  26. angr/utils/graph.py +10 -12
  27. angr/utils/ssa/__init__.py +6 -1
  28. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/METADATA +6 -6
  29. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/RECORD +33 -32
  30. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/WHEEL +1 -1
  31. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/entry_points.txt +0 -0
  32. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/licenses/LICENSE +0 -0
  33. {angr-9.2.150.dist-info → angr-9.2.153.dist-info}/top_level.txt +0 -0
angr/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # pylint: disable=wrong-import-position
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "9.2.150"
5
+ __version__ = "9.2.153"
6
6
 
7
7
  if bytes is str:
8
8
  raise Exception(
@@ -21,6 +21,7 @@ from angr.calling_conventions import (
21
21
  default_cc,
22
22
  SimCCMicrosoftThiscall,
23
23
  )
24
+ from angr.errors import SimTranslationError
24
25
  from angr.sim_type import (
25
26
  SimTypeCppFunction,
26
27
  SimTypeInt,
@@ -585,16 +586,23 @@ class CallingConventionAnalysis(Analysis):
585
586
  # include its successor.
586
587
 
587
588
  # Re-lift the target block
588
- dst_bb = self.project.factory.block(dst.addr, func.get_block_size(dst.addr), opt_level=1)
589
+ dst_block_size = func.get_block_size(dst.addr)
590
+ if dst_block_size is not None and dst_block_size > 0:
591
+ dst_bb = self.project.factory.block(dst.addr, dst_block_size, opt_level=1)
592
+ try:
593
+ vex_block = dst_bb.vex
594
+ except SimTranslationError:
595
+ # failed to lift the block
596
+ continue
589
597
 
590
- # If there is only one 'IMark' statement in vex --> the target block contains only direct jump
591
- if (
592
- len(dst_bb.vex.statements) == 1
593
- and dst_bb.vex.statements[0].tag == "Ist_IMark"
594
- and func.graph.out_degree(dst) == 1
595
- ):
596
- for _, jmp_dst, jmp_data in func_graph.out_edges(dst, data=True):
597
- subgraph.add_edge(dst, jmp_dst, **jmp_data)
598
+ # If there is only one 'IMark' statement in vex --> the target block contains only direct jump
599
+ if (
600
+ len(vex_block.statements) == 1
601
+ and vex_block.statements[0].tag == "Ist_IMark"
602
+ and func.graph.out_degree(dst) == 1
603
+ ):
604
+ for _, jmp_dst, jmp_data in func_graph.out_edges(dst, data=True):
605
+ subgraph.add_edge(dst, jmp_dst, **jmp_data)
598
606
 
599
607
  return subgraph
600
608
 
@@ -1515,7 +1515,7 @@ class CFGBase(Analysis):
1515
1515
  Revisit the entire control flow graph, create Function instances accordingly, and correctly put blocks into
1516
1516
  each function.
1517
1517
 
1518
- Although Function objects are crated during the CFG recovery, they are neither sound nor accurate. With a
1518
+ Although Function objects are created during the CFG recovery, they are neither sound nor accurate. With a
1519
1519
  pre-constructed CFG, this method rebuilds all functions bearing the following rules:
1520
1520
 
1521
1521
  - A block may only belong to one function.
@@ -1554,6 +1554,45 @@ class CFGFast(ForwardAnalysis[CFGNode, CFGNode, CFGJob, int], CFGBase): # pylin
1554
1554
  }:
1555
1555
  func.info["is_alloca_probe"] = True
1556
1556
 
1557
+ # determine if the function is _guard_xfg_dispatch_icall_nop or _guard_xfg_dispatch_icall_fptr
1558
+ if func is not None and not func.is_simprocedure and len(func.block_addrs_set) in {1, 2}:
1559
+ # _guard_xfg_dispatch_icall_nop jumps to _guard_xfg_dispatch_icall_fptr, but we may or may not identify
1560
+ # _guard_xfg_dispatch_icall_fptr as a separate function.
1561
+ # so, two possibilities:
1562
+ # - _guard_xfg_dispatch_icall_nop is a function with one block and jumps to
1563
+ # _guard_xfg_dispatch_icall_fptr.
1564
+ # - _guard_xfg_dispatch_icall_nop is a function with 2 blocks, and the second block is the body of
1565
+ # _guard_xfg_dispatch_icall_fptr.
1566
+ try:
1567
+ block = func.get_block(func.addr)
1568
+ except SimTranslationError:
1569
+ block = None
1570
+ if block is not None and block.instructions == 1:
1571
+ insn = block.capstone.insns[0]
1572
+ if block.bytes == b"\xff\xe0":
1573
+ func.info["jmp_rax"] = True
1574
+ elif (
1575
+ insn.mnemonic == "jmp"
1576
+ and insn.operands[0].type == capstone.x86.X86_OP_MEM
1577
+ and insn.operands[0].mem.base == capstone.x86.X86_REG_RIP
1578
+ and insn.operands[0].mem.disp > 0
1579
+ and insn.operands[0].mem.index == 0
1580
+ ):
1581
+ # where is it jumping to?
1582
+ jumpout_targets = list(self.graph.successors(self.model.get_any_node(func.addr)))
1583
+ if len(jumpout_targets) == 1:
1584
+ jumpout_target = jumpout_targets[0].addr
1585
+ if len(func.block_addrs_set) == 1 and len(func.jumpout_sites) == 1:
1586
+ if (
1587
+ self.kb.functions.contains_addr(jumpout_target)
1588
+ and self.kb.functions.get_by_addr(jumpout_target).get_block(jumpout_target).bytes
1589
+ == b"\xff\xe0"
1590
+ ):
1591
+ func.info["jmp_rax"] = True
1592
+ elif len(func.block_addrs_set) == 2 and func.get_block(jumpout_target).bytes == b"\xff\xe0":
1593
+ # check the second block and ensure it's jmp rax
1594
+ func.info["jmp_rax"] = True
1595
+
1557
1596
  elif self.project.arch.name == "X86":
1558
1597
  # determine if the function is __alloca_probe
1559
1598
  func = self.kb.functions.get_by_addr(func_addr) if self.kb.functions.contains_addr(func_addr) else None
@@ -203,7 +203,6 @@ class AILSimplifier(Analysis):
203
203
  AILGraphWalker(self.func_graph, _handler, replace_nodes=True).walk()
204
204
  self.blocks = {}
205
205
 
206
- @timethis
207
206
  def _compute_reaching_definitions(self) -> SRDAModel:
208
207
  # Computing reaching definitions or return the cached one
209
208
  if self._reaching_definitions is not None:
@@ -412,6 +412,45 @@ class AMD64CCallRewriter(CCallRewriterBase):
412
412
  )
413
413
  return Expr.Convert(None, r.bits, ccall.bits, False, r, **ccall.tags)
414
414
 
415
+ elif (
416
+ cond_v == AMD64_CondTypes["CondNS"]
417
+ and op_v
418
+ in {
419
+ AMD64_OpTypes["G_CC_OP_LOGICB"],
420
+ AMD64_OpTypes["G_CC_OP_LOGICW"],
421
+ AMD64_OpTypes["G_CC_OP_LOGICL"],
422
+ AMD64_OpTypes["G_CC_OP_LOGICQ"],
423
+ }
424
+ and isinstance(dep_2, Expr.Const)
425
+ and dep_2.value == 0
426
+ ):
427
+ # dep_1 >= 0
428
+ dep_1 = self._fix_size(
429
+ dep_1,
430
+ op_v,
431
+ AMD64_OpTypes["G_CC_OP_LOGICB"],
432
+ AMD64_OpTypes["G_CC_OP_LOGICW"],
433
+ AMD64_OpTypes["G_CC_OP_LOGICL"],
434
+ ccall.tags,
435
+ )
436
+ dep_2 = self._fix_size(
437
+ dep_2,
438
+ op_v,
439
+ AMD64_OpTypes["G_CC_OP_LOGICB"],
440
+ AMD64_OpTypes["G_CC_OP_LOGICW"],
441
+ AMD64_OpTypes["G_CC_OP_LOGICL"],
442
+ ccall.tags,
443
+ )
444
+
445
+ r = Expr.BinaryOp(
446
+ ccall.idx,
447
+ "CmpGE",
448
+ (dep_1, dep_2),
449
+ True,
450
+ **ccall.tags,
451
+ )
452
+ return Expr.Convert(None, r.bits, ccall.bits, False, r, **ccall.tags)
453
+
415
454
  elif ccall.callee == "amd64g_calculate_rflags_c":
416
455
  # calculate the carry flag
417
456
  op = ccall.operands[0]
@@ -483,6 +483,11 @@ class Clinic(Analysis):
483
483
  arg_vvars = self._create_function_argument_vvars(arg_list)
484
484
  func_args = {arg_vvar for arg_vvar, _ in arg_vvars.values()}
485
485
 
486
+ # duplicate orphaned conditional jump blocks
487
+ ail_graph = self._duplicate_orphaned_cond_jumps(ail_graph)
488
+ # rewrite jmp_rax function calls
489
+ ail_graph = self._rewrite_jump_rax_calls(ail_graph)
490
+
486
491
  # Transform the graph into partial SSA form
487
492
  self._update_progress(35.0, text="Transforming to partial-SSA form")
488
493
  ail_graph = self._transform_to_ssa_level0(ail_graph, func_args)
@@ -927,7 +932,7 @@ class Clinic(Analysis):
927
932
  self.kb.callsite_prototypes.set_prototype(callsite.addr, cc.cc, cc.prototype, manual=False)
928
933
  if func_graph is not None and cc.prototype.returnty is not None:
929
934
  # patch the AIL call statement if we can find one
930
- callsite_ail_block: ailment.Block = next(
935
+ callsite_ail_block: ailment.Block | None = next(
931
936
  iter(bb for bb in func_graph if bb.addr == callsite.addr), None
932
937
  )
933
938
  if callsite_ail_block is not None and callsite_ail_block.statements:
@@ -1002,6 +1007,7 @@ class Clinic(Analysis):
1002
1007
  :return: None
1003
1008
  """
1004
1009
  assert self._func_graph is not None
1010
+ assert self._blocks_by_addr_and_size is not None
1005
1011
 
1006
1012
  for block_node in self._func_graph.nodes():
1007
1013
  ail_block = self._convert(block_node)
@@ -1892,6 +1898,19 @@ class Clinic(Analysis):
1892
1898
  self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, stmt.dst)
1893
1899
  self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, stmt.src)
1894
1900
 
1901
+ elif stmt_type is ailment.Stmt.CAS:
1902
+ for expr in [
1903
+ stmt.addr,
1904
+ stmt.data_lo,
1905
+ stmt.data_hi,
1906
+ stmt.expd_lo,
1907
+ stmt.expd_hi,
1908
+ stmt.old_lo,
1909
+ stmt.old_hi,
1910
+ ]:
1911
+ if expr is not None:
1912
+ self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, expr)
1913
+
1895
1914
  elif stmt_type is ailment.Stmt.ConditionalJump:
1896
1915
  self._link_variables_on_expr(variable_manager, global_variables, block, stmt_idx, stmt, stmt.condition)
1897
1916
 
@@ -2123,6 +2142,86 @@ class Clinic(Analysis):
2123
2142
 
2124
2143
  return graph
2125
2144
 
2145
+ @staticmethod
2146
+ def _duplicate_orphaned_cond_jumps(ail_graph) -> networkx.DiGraph:
2147
+ """
2148
+ Find conditional jumps that are orphaned (e.g., being the only instruction of the block). If these blocks have
2149
+ multiple predecessors, duplicate them to all predecessors. This is a workaround for cases where these
2150
+ conditional jumps rely on comparisons in more than one predecessor and we cannot resolve ccalls into
2151
+ comparisons.
2152
+
2153
+ This pass runs before any SSA transformations.
2154
+
2155
+ # 140017162 jz short 1400171e1
2156
+ """
2157
+
2158
+ for block in list(ail_graph):
2159
+ if len(block.statements) > 1 and block.statements[0].ins_addr == block.statements[-1].ins_addr:
2160
+ preds = list(ail_graph.predecessors(block))
2161
+ if len(preds) > 1 and block not in preds:
2162
+ has_ccall = any(
2163
+ isinstance(stmt, ailment.Stmt.Assignment)
2164
+ and isinstance(stmt.src, ailment.Expr.VEXCCallExpression)
2165
+ for stmt in block.statements
2166
+ )
2167
+ if has_ccall:
2168
+ # duplicate this block to its predecessors!
2169
+ preds = sorted(preds, key=lambda x: x.addr)
2170
+ succs = sorted(ail_graph.successors(block), key=lambda x: x.addr)
2171
+ # FIXME: We should track block IDs globally and ensure block IDs do not collide
2172
+ block_idx_start = block.idx + 1 if block.idx is not None else 1
2173
+ for pred in preds[1:]:
2174
+ ail_graph.remove_edge(pred, block)
2175
+ new_block = block.copy()
2176
+ new_block.idx = block_idx_start
2177
+ block_idx_start += 1
2178
+ ail_graph.add_edge(pred, new_block)
2179
+ for succ in succs:
2180
+ ail_graph.add_edge(new_block, succ if succ is not block else new_block)
2181
+
2182
+ return ail_graph
2183
+
2184
+ def _rewrite_jump_rax_calls(self, ail_graph: networkx.DiGraph) -> networkx.DiGraph:
2185
+ """
2186
+ Rewrite calls to special functions (e.g., guard_dispatch_icall_nop) into `call rax`.
2187
+ """
2188
+
2189
+ if self.project.arch.name != "AMD64":
2190
+ return ail_graph
2191
+ if self._cfg is None:
2192
+ return ail_graph
2193
+
2194
+ for block in ail_graph:
2195
+ if not block.statements:
2196
+ continue
2197
+ assert block.addr is not None
2198
+ last_stmt = block.statements[-1]
2199
+ if isinstance(last_stmt, ailment.Stmt.Call):
2200
+ # we can't examine the call target at this point because constant propagation hasn't run yet; we consult
2201
+ # the CFG instead
2202
+ callsite_node = self._cfg.get_any_node(block.addr, anyaddr=True)
2203
+ if callsite_node is None:
2204
+ break
2205
+ callees = self._cfg.get_successors(callsite_node, jumpkind="Ijk_Call")
2206
+ if len(callees) != 1:
2207
+ break
2208
+ callee = callees[0].addr
2209
+ if self.kb.functions.contains_addr(callee):
2210
+ callee_func = self.kb.functions.get_by_addr(callee)
2211
+ if callee_func.info.get("jmp_rax", False) is True:
2212
+ # rewrite this statement into Call(rax)
2213
+ call_stmt = last_stmt.copy()
2214
+ call_stmt.target = ailment.Expr.Register(
2215
+ self._ail_manager.next_atom(),
2216
+ None,
2217
+ self.project.arch.registers["rax"][0],
2218
+ 64,
2219
+ ins_addr=call_stmt.ins_addr,
2220
+ )
2221
+ block.statements[-1] = call_stmt
2222
+
2223
+ return ail_graph
2224
+
2126
2225
  def _rewrite_ite_expressions(self, ail_graph):
2127
2226
  cfg = self._cfg
2128
2227
  for block in list(ail_graph):
@@ -2130,11 +2229,16 @@ class Clinic(Analysis):
2130
2229
  continue
2131
2230
 
2132
2231
  ite_ins_addrs = []
2232
+ cas_ins_addrs = set()
2133
2233
  for stmt in block.statements:
2134
- if (
2234
+ if isinstance(stmt, ailment.Stmt.CAS):
2235
+ # we do not rewrite ITE statements that are caused by CAS statements
2236
+ cas_ins_addrs.add(stmt.ins_addr)
2237
+ elif (
2135
2238
  isinstance(stmt, ailment.Stmt.Assignment)
2136
2239
  and isinstance(stmt.src, ailment.Expr.ITE)
2137
2240
  and stmt.ins_addr not in ite_ins_addrs
2241
+ and stmt.ins_addr not in cas_ins_addrs
2138
2242
  ):
2139
2243
  ite_ins_addrs.append(stmt.ins_addr)
2140
2244
 
@@ -2998,6 +3102,12 @@ class Clinic(Analysis):
2998
3102
  and last_stmt.addr.offset < 0
2999
3103
  and isinstance(last_stmt.data, ailment.Expr.Const)
3000
3104
  and last_stmt.data.value == succ.addr
3105
+ ) or (
3106
+ isinstance(last_stmt, ailment.Stmt.Assignment)
3107
+ and last_stmt.dst.was_stack
3108
+ and last_stmt.dst.stack_offset < 0
3109
+ and isinstance(last_stmt.src, ailment.Expr.Const)
3110
+ and last_stmt.src.value == succ.addr
3001
3111
  ):
3002
3112
  # remove the statement that pushes the return address
3003
3113
  node.statements = node.statements[:-1]
@@ -3031,6 +3141,12 @@ class Clinic(Analysis):
3031
3141
  and last_stmt.addr.offset < 0
3032
3142
  and isinstance(last_stmt.data, ailment.Expr.Const)
3033
3143
  and last_stmt.data.value == succ.addr
3144
+ ) or (
3145
+ isinstance(last_stmt, ailment.Stmt.Assignment)
3146
+ and last_stmt.dst.was_stack
3147
+ and last_stmt.dst.stack_offset < 0
3148
+ and isinstance(last_stmt.src, ailment.Expr.Const)
3149
+ and last_stmt.src.value == succ.addr
3034
3150
  ):
3035
3151
  # remove the statement that pushes the return address
3036
3152
  node.statements = node.statements[:-1]
@@ -1,4 +1,4 @@
1
- # pylint:disable=unused-argument,no-self-use
1
+ # pylint:disable=unused-argument,no-self-use,too-many-boolean-expressions
2
2
  from __future__ import annotations
3
3
  import logging
4
4
 
@@ -8,12 +8,14 @@ from ailment.statement import (
8
8
  Assignment,
9
9
  Store,
10
10
  Call,
11
+ CAS,
11
12
  Return,
12
13
  ConditionalJump,
13
14
  DirtyStatement,
14
15
  WeakAssignment,
15
16
  )
16
17
  from ailment.expression import (
18
+ Atom,
17
19
  Expression,
18
20
  VirtualVariable,
19
21
  Load,
@@ -121,6 +123,40 @@ class SimEngineDephiRewriting(SimEngineNostmtAIL[None, Expression | None, Statem
121
123
  )
122
124
  return None
123
125
 
126
+ def _handle_stmt_CAS(self, stmt: CAS) -> CAS | None:
127
+ new_addr = self._expr(stmt.addr)
128
+ new_data_lo = self._expr(stmt.data_lo)
129
+ new_data_hi = self._expr(stmt.data_hi) if stmt.data_hi is not None else None
130
+ new_expd_lo = self._expr(stmt.expd_lo)
131
+ new_expd_hi = self._expr(stmt.expd_hi) if stmt.expd_hi is not None else None
132
+ new_old_lo = self._expr(stmt.old_lo)
133
+ new_old_hi = self._expr(stmt.old_hi) if stmt.old_hi is not None else None
134
+ assert new_old_lo is None or isinstance(new_old_lo, Atom)
135
+ assert new_old_hi is None or isinstance(new_old_hi, Atom)
136
+
137
+ if (
138
+ new_addr is not None
139
+ or new_old_lo is not None
140
+ or new_old_hi is not None
141
+ or new_data_lo is not None
142
+ or new_data_hi is not None
143
+ or new_expd_lo is not None
144
+ or new_expd_hi is not None
145
+ ):
146
+ return CAS(
147
+ stmt.idx,
148
+ stmt.addr if new_addr is None else new_addr,
149
+ stmt.data_lo if new_data_lo is None else new_data_lo,
150
+ stmt.data_hi if new_data_hi is None else new_data_hi,
151
+ stmt.expd_lo if new_expd_lo is None else new_expd_lo,
152
+ stmt.expd_hi if new_expd_hi is None else new_expd_hi,
153
+ stmt.old_lo if new_old_lo is None else new_old_lo,
154
+ stmt.old_hi if new_old_hi is None else new_old_hi,
155
+ stmt.endness,
156
+ **stmt.tags,
157
+ )
158
+ return None
159
+
124
160
  def _handle_stmt_Store(self, stmt):
125
161
  new_addr = self._expr(stmt.addr)
126
162
  new_data = self._expr(stmt.data)
@@ -179,6 +215,7 @@ class SimEngineDephiRewriting(SimEngineNostmtAIL[None, Expression | None, Statem
179
215
  dirty = self._expr(stmt.dirty)
180
216
  if dirty is None or dirty is stmt.dirty:
181
217
  return None
218
+ assert isinstance(dirty, DirtyExpression)
182
219
  return DirtyStatement(stmt.idx, dirty, **stmt.tags)
183
220
 
184
221
  def _handle_expr_Load(self, expr):
@@ -107,6 +107,12 @@ class ConditionConstantPropagation(OptimizationPass):
107
107
  cconds_by_src[src] = []
108
108
  cconds_by_src[src].append(ccond)
109
109
 
110
+ # eliminate sources with more than one in-edges; this is because the condition may not hold on all in-edges!
111
+ for src in list(cconds_by_src):
112
+ block = self._get_block(src[0], idx=src[1])
113
+ if block is not None and block in self._graph and self._graph.in_degree[block] > 1:
114
+ del cconds_by_src[src]
115
+
110
116
  # eliminate conflicting conditions
111
117
  for src in list(cconds_by_src):
112
118
  cconds = cconds_by_src[src]
@@ -86,6 +86,11 @@ class SimplifierAILEngine(
86
86
 
87
87
  return stmt
88
88
 
89
+ def _handle_stmt_CAS(self, stmt: ailment.statement.CAS) -> ailment.statement.CAS:
90
+ # we assume that we never have to deal with CAS statements at this point; they should have been rewritten to
91
+ # intrinsics
92
+ return stmt
93
+
89
94
  def _handle_stmt_Store(self, stmt):
90
95
  addr = self._expr(stmt.addr)
91
96
  data = self._expr(stmt.data)
@@ -8,6 +8,7 @@ from .a_sub_a_div_const_mul_const import ASubADivConstMulConst
8
8
  from .a_sub_a_shr_const_shr_const import ASubAShrConstShrConst
9
9
  from .arm_cmpf import ARMCmpF
10
10
  from .bswap import Bswap
11
+ from .cas_intrinsics import CASIntrinsics
11
12
  from .coalesce_same_cascading_ifs import CoalesceSameCascadingIfs
12
13
  from .constant_derefs import ConstantDereferences
13
14
  from .const_mull_a_shift import ConstMullAShift
@@ -64,6 +65,7 @@ ALL_PEEPHOLE_OPTS: list[type[PeepholeOptimizationExprBase]] = [
64
65
  ASubAShrConstShrConst,
65
66
  ARMCmpF,
66
67
  Bswap,
68
+ CASIntrinsics,
67
69
  CoalesceSameCascadingIfs,
68
70
  ConstantDereferences,
69
71
  ConstMullAShift,
@@ -0,0 +1,115 @@
1
+ # pylint:disable=arguments-differ,too-many-boolean-expressions
2
+ from __future__ import annotations
3
+
4
+ from ailment.expression import BinaryOp, Load
5
+ from ailment.statement import CAS, ConditionalJump, Statement, Assignment, Call
6
+
7
+ from .base import PeepholeOptimizationMultiStmtBase
8
+
9
+
10
+ _INTRINSICS_NAMES = {
11
+ "xchg": {"Win32": "InterlockedExchange", "Linux": "atomic_exchange"},
12
+ "cmpxchg": {"Win32": "InterlockedCompareExchange", "Linux": "atomic_compare_exchange"},
13
+ }
14
+
15
+
16
+ class CASIntrinsics(PeepholeOptimizationMultiStmtBase):
17
+ """
18
+ Rewrite lock-prefixed instructions (or rather, their VEX/AIL forms) into intrinsic calls.
19
+
20
+ Case 1.
21
+
22
+ mov eax, r12d
23
+ 0x140014b57: xchg eax, [0x14000365f8]
24
+
25
+ LABEL_0x140014b57:
26
+ CAS(0x1400365f8<64>, Conv(64->32, vvar_365{reg 112}), Load(addr=0x1400365f8<64>, size=4, endness=Iend_LE),
27
+ vvar_27756)
28
+ if (CasCmpNE(vvar_27756, g_1400365f8))
29
+ goto LABEL_0x140014b57;
30
+
31
+ => vvar_27756 = _InterlockedExchange(0x1400365f8, vvar_365{reg 112})
32
+
33
+
34
+ Case 2.
35
+
36
+ lock cmpxchg cs:g_WarbirdSecureFunctionsLock, r14d
37
+
38
+ CAS(0x1400365f8<64>, 0x1<32>, 0x0<32>, vvar_27751)
39
+
40
+ => var_27751 = _InterlockedCompareExchange(0x1400365f8, 0x1<32>, 0x0<32>)
41
+ """
42
+
43
+ __slots__ = ()
44
+
45
+ NAME = "Rewrite compare-and-swap instructions into intrinsics."
46
+ stmt_classes = ((CAS, ConditionalJump), (CAS, Statement))
47
+
48
+ def optimize(self, stmts: list[Statement], stmt_idx: int | None = None, block=None, **kwargs):
49
+ assert len(stmts) == 2
50
+ cas_stmt = stmts[0]
51
+ next_stmt = stmts[1]
52
+ assert isinstance(cas_stmt, CAS)
53
+
54
+ # TODO: We ignored endianness. Are there cases where the endianness is different from the host's?
55
+
56
+ if (
57
+ isinstance(next_stmt, ConditionalJump)
58
+ and isinstance(next_stmt.condition, BinaryOp)
59
+ and next_stmt.condition.op == "CasCmpNE"
60
+ and next_stmt.ins_addr == cas_stmt.ins_addr
61
+ ):
62
+ addr = cas_stmt.addr
63
+ if (
64
+ isinstance(cas_stmt.expd_lo, Load)
65
+ and cas_stmt.expd_lo.addr.likes(addr)
66
+ and isinstance(next_stmt.condition.operands[1], Load)
67
+ and next_stmt.condition.operands[1].addr.likes(addr)
68
+ and cas_stmt.old_lo.likes(next_stmt.condition.operands[0])
69
+ and cas_stmt.old_hi is None
70
+ ):
71
+ # TODO: Support cases where cas_stmt.old_hi is not None
72
+ # Case 1
73
+ call_expr = Call(
74
+ cas_stmt.idx,
75
+ self._get_instrincs_name("xchg"),
76
+ args=[addr, cas_stmt.data_lo],
77
+ bits=cas_stmt.bits,
78
+ ins_addr=cas_stmt.ins_addr,
79
+ )
80
+ stmt = Assignment(cas_stmt.idx, cas_stmt.old_lo, call_expr, **cas_stmt.tags)
81
+ return [stmt]
82
+
83
+ if next_stmt.ins_addr <= cas_stmt.ins_addr:
84
+ # avoid matching against statements prematurely
85
+ return None
86
+
87
+ if cas_stmt.old_hi is None:
88
+ # TODO: Support cases where cas_stmt.old_hi is not None
89
+ call_expr = Call(
90
+ cas_stmt.idx,
91
+ self._get_instrincs_name("cmpxchg"),
92
+ args=[
93
+ cas_stmt.addr,
94
+ cas_stmt.data_lo,
95
+ cas_stmt.expd_lo,
96
+ ],
97
+ bits=cas_stmt.bits,
98
+ ins_addr=cas_stmt.ins_addr,
99
+ )
100
+ stmt = Assignment(cas_stmt.idx, cas_stmt.old_lo, call_expr, **cas_stmt.tags)
101
+ return [stmt, next_stmt]
102
+
103
+ return None
104
+
105
+ def _get_instrincs_name(self, mnemonic: str) -> str:
106
+ if mnemonic in _INTRINSICS_NAMES:
107
+ os = (
108
+ self.project.simos.name
109
+ if self.project is not None and self.project.simos is not None and self.project.simos.name is not None
110
+ else "Linux"
111
+ )
112
+ if os not in _INTRINSICS_NAMES[mnemonic]:
113
+ os = "Linux"
114
+ return _INTRINSICS_NAMES[mnemonic][os]
115
+ return mnemonic