Repository: angr/angr

Issue:
Exception raised in jump table resolver
After upgrading to angr 7.8.6.23 from 7.8.6.16, the following code:

```
import angr
p = angr.Project(r"Crash", load_options={"auto_load_libs": False})
cfg = p.analyses.CFGFast()
```
crashes with:

```
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\analysis.py", line 108, in __call__
    oself.__init__(*args, **kwargs)
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\cfg\cfg_fast.py", line 909, in __init__
    self._analyze()
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\forward_analysis.py", line 552, in _analyze
    self._analysis_core_baremetal()
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\forward_analysis.py", line 653, in _analysis_core_baremetal
    self._job_queue_empty()
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\cfg\cfg_fast.py", line 1385, in _job_queue_empty
    jump_targets = list(set(self._process_indirect_jumps()))
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\cfg\cfg_fast.py", line 2549, in _process_indirect_jumps
    resolved, targets = resolver.resolve(self, jump.addr, jump.func_addr, block, jump.jumpkind)
  File "C:\python27x64ActiveState\lib\site-packages\angr\analyses\cfg\indirect_jump_resolvers\jumptable.py", line 176, in resolve
    load_addr_tmp = load_stmt.addr.tmp
AttributeError: 'Const' object has no attribute 'tmp'
```

I have manually applied the changes in the [17a43e93cf6ff9b5f9a9846a00fa53e54ed1095c commit](https://github.com/angr/angr/commit/17a43e93cf6ff9b5f9a9846a00fa53e54ed1095c#diff-a64a57e378cbee948c40d83b36d3c4aa)  but it still doesn't work. 


[puts.zip](https://github.com/angr/angr/files/2136669/puts.zip)



Discussion:
Thanks for reporting. It is indeed a bug.

Resolving patch:
diff --git a/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py b/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
--- a/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
+++ b/angr/analyses/cfg/indirect_jump_resolvers/jumptable.py
@@ -170,22 +170,10 @@ def resolve(self, cfg, addr, func_addr, block, jumpkind):
                 state = all_states[0]  # Just take the first state
 
                 # Parse the memory load statement
-                if isinstance(load_stmt, pyvex.IRStmt.WrTmp):
-                    load_addr_tmp = load_stmt.data.addr.tmp
-                elif isinstance(load_stmt, pyvex.IRStmt.LoadG):
-                    load_addr_tmp = load_stmt.addr.tmp
-                else:
-                    raise TypeError("Unsupported address loading statement type %s." % type(load_stmt))
-                if load_addr_tmp not in state.scratch.temps:
-                    # the tmp variable is not there... umm...
+                jump_addr = self._parse_load_statement(load_stmt, state)
+                if jump_addr is None:
                     continue
-                jump_addr = state.scratch.temps[load_addr_tmp]
                 all_targets = [ ]
-                if isinstance(load_stmt, pyvex.IRStmt.LoadG):
-                    # LoadG comes with a guard. We should apply this guard to the load expression
-                    guard_tmp = load_stmt.guard.tmp
-                    guard = state.scratch.temps[guard_tmp] != 0
-                    jump_addr = state.memory._apply_condition_to_symbolic_addr(jump_addr, guard)
                 total_cases = jump_addr._model_vsa.cardinality
 
                 if total_cases > self._max_targets:
@@ -201,7 +189,7 @@ def resolve(self, cfg, addr, func_addr, block, jumpkind):
                     # jump_target = state.se.SI(bits=64, lower_bound=jump_base_addr, upper_bound=jump_base_addr +
                     # (total_cases - 1) * 8, stride=8)
 
-                jump_table = []
+                jump_table = [ ]
 
                 min_jump_target = state.se.min(jump_addr)
                 max_jump_target = state.se.max(jump_addr)
@@ -225,10 +213,15 @@ def resolve(self, cfg, addr, func_addr, block, jumpkind):
 
                 # write to the IndirectJump object in CFG
                 ij = cfg.indirect_jumps[addr]
-                ij.jumptable = True
-                ij.jumptable_addr = state.se.min(jump_addr)
-                ij.resolved_targets = set(jump_table)
-                ij.jumptable_entries = jump_table
+                if total_cases > 1:
+                    # It can be considered a jump table only if there are more than one jump target
+                    ij.jumptable = True
+                    ij.jumptable_addr = state.se.min(jump_addr)
+                    ij.resolved_targets = set(jump_table)
+                    ij.jumptable_entries = jump_table
+                else:
+                    ij.jumptable = False
+                    ij.resolved_targets = set(jump_table)
 
                 return True, all_targets
 
@@ -348,3 +341,53 @@ def _initial_state(self, src_irsb):
         )
 
         return state
+
+    @staticmethod
+    def _parse_load_statement(load_stmt, state):
+        """
+        Parse a memory load VEX statement and get the jump target addresses.
+
+        :param load_stmt:   The VEX statement for loading the jump target addresses.
+        :param state:       The SimState instance (in static mode).
+        :return:            A tuple of an abstract value (or a concrete value) representing the jump target addresses,
+                            and a set of extra concrete targets. Return (None, None) if we fail to parse the statement.
+        """
+
+        # The jump table address is stored in a tmp. In this case, we find the jump-target loading tmp.
+        load_addr_tmp = None
+
+        if isinstance(load_stmt, pyvex.IRStmt.WrTmp):
+            load_addr_tmp = load_stmt.data.addr.tmp
+        elif isinstance(load_stmt, pyvex.IRStmt.LoadG):
+            if type(load_stmt.addr) is pyvex.IRExpr.RdTmp:
+                load_addr_tmp = load_stmt.addr.tmp
+            elif type(load_stmt.addr) is pyvex.IRExpr.Const:
+                # It's directly loading from a constant address
+                # e.g.,
+                #  4352c     SUB     R1, R11, #0x1000
+                #  43530     LDRHI   R3, =loc_45450
+                #  ...
+                #  43540     MOV     PC, R3
+                #
+                # It's not a jump table, but we resolve it anyway
+                # TODO: We should develop an ARM-specific indirect jump resolver in this case
+                # Note that this block has two branches: One goes to 45450, the other one goes to whatever the original
+                # value of R3 is. Some intensive data-flow analysis is required in this case.
+                jump_target_addr = load_stmt.addr.con.value
+                return state.se.BVV(jump_target_addr, state.arch.bits)
+        else:
+            raise TypeError("Unsupported address loading statement type %s." % type(load_stmt))
+
+        if load_addr_tmp not in state.scratch.temps:
+            # the tmp variable is not there... umm...
+            return None
+
+        jump_addr = state.scratch.temps[load_addr_tmp]
+
+        if isinstance(load_stmt, pyvex.IRStmt.LoadG):
+            # LoadG comes with a guard. We should apply this guard to the load expression
+            guard_tmp = load_stmt.guard.tmp
+            guard = state.scratch.temps[guard_tmp] != 0
+            jump_addr = state.memory._apply_condition_to_symbolic_addr(jump_addr, guard)
+
+        return jump_addr


Test patch: