mirror of
https://github.com/angr/angr
synced 2026-08-17 12:23:11 -04:00
182 lines
6.4 KiB
Python
182 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
# pylint: disable=missing-class-docstring,no-self-use,no-member
|
|
from __future__ import annotations
|
|
|
|
__package__ = __package__ or "tests.analyses.decompiler" # pylint:disable=redefined-builtin
|
|
|
|
import os
|
|
import unittest
|
|
|
|
import angr
|
|
from angr.ailment import Manager
|
|
from angr.ailment.block import Block
|
|
from angr.ailment.expression import (
|
|
Const,
|
|
DirtyExpression,
|
|
Insert,
|
|
VirtualVariable,
|
|
VirtualVariableCategory,
|
|
)
|
|
from angr.ailment.statement import CAS, DirtyStatement, Jump, WeakAssignment
|
|
from angr.analyses.decompiler.structurer_nodes import (
|
|
IncompleteSwitchCaseHeadStatement,
|
|
IncompleteSwitchCaseNode,
|
|
SequenceNode,
|
|
)
|
|
from tests.common import bin_location, load_project_with_scoped_cfg, print_decompilation_result
|
|
|
|
test_location = os.path.join(bin_location, "tests")
|
|
|
|
# what the Rust backend emits for a node it has no handler for
|
|
PLACEHOLDER = "unsupported instruction"
|
|
|
|
|
|
def _render(node):
|
|
return "".join(chunk for chunk, _ in node.c_repr_chunks())
|
|
|
|
|
|
class TestRustCodegenHandlers(unittest.TestCase):
|
|
"""
|
|
_handle_AILBlock() substitutes a placeholder for any statement the backend has no handler for, so a missing
|
|
handler silently drops whatever the statement contained.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# any binary will do: we only need a constructed Rust code generator to drive handlers with
|
|
proj = angr.Project(os.path.join(test_location, "x86_64", "fauxware"), auto_load_libs=False)
|
|
cfg = proj.analyses.CFGFast(normalize=True, show_progressbar=False)
|
|
dec = proj.analyses.Decompiler(proj.kb.functions["main"], cfg=cfg.model, flavor="rust", fail_fast=True)
|
|
assert dec.codegen is not None
|
|
cls.proj = proj
|
|
cls.codegen = dec.codegen
|
|
|
|
def _manager(self):
|
|
return Manager(arch=self.proj.arch)
|
|
|
|
@staticmethod
|
|
def _vvar(m, varid, bits=64, category=VirtualVariableCategory.REGISTER):
|
|
return VirtualVariable(m.next_atom(), varid, bits, category)
|
|
|
|
def test_handler_table_covers_everything_the_c_backend_covers(self):
|
|
"""A missing handler degrades silently, so guard the whole class rather than one node type at a time."""
|
|
proj = self.proj
|
|
cfg = proj.kb.cfgs.get_most_accurate()
|
|
c_dec = proj.analyses.Decompiler(proj.kb.functions["main"], cfg=cfg, flavor="pseudocode", fail_fast=True)
|
|
assert c_dec.codegen is not None
|
|
|
|
missing = set(c_dec.codegen._handlers) - set(self.codegen._handlers)
|
|
assert not missing, f"Rust backend has no handler for {sorted(str(k) for k in missing)}"
|
|
|
|
def test_insert(self):
|
|
m = self._manager()
|
|
expr = Insert(
|
|
m.next_atom(),
|
|
self._vvar(m, 1),
|
|
Const(m.next_atom(), 0, 8),
|
|
self._vvar(m, 2, bits=32),
|
|
"Iend_LE",
|
|
)
|
|
|
|
out = _render(self.codegen._handle(expr))
|
|
assert "_INSERT(" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_dirty_statement(self):
|
|
m = self._manager()
|
|
dirty = DirtyExpression(m.next_atom(), "amd64g_dirtyhelper_RDTSC", [], bits=64)
|
|
stmt = DirtyStatement(m.next_atom(), dirty, ins_addr=0x400100)
|
|
|
|
out = _render(self.codegen._handle(stmt, is_expr=False))
|
|
assert "amd64g_dirtyhelper_RDTSC" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_weak_assignment(self):
|
|
m = self._manager()
|
|
stmt = WeakAssignment(m.next_atom(), self._vvar(m, 1), self._vvar(m, 2), ins_addr=0x400100)
|
|
|
|
out = _render(self.codegen._handle(stmt, is_expr=False))
|
|
assert "=" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_cas(self):
|
|
m = self._manager()
|
|
stmt = CAS(
|
|
m.next_atom(),
|
|
Const(m.next_atom(), 0x1000, 64),
|
|
Const(m.next_atom(), 1, 32),
|
|
None,
|
|
Const(m.next_atom(), 0, 32),
|
|
None,
|
|
self._vvar(m, 2, bits=32),
|
|
None,
|
|
"Iend_LE",
|
|
ins_addr=0x400100,
|
|
)
|
|
|
|
out = _render(self.codegen._handle(stmt, is_expr=False))
|
|
assert "atomic_compare_exchange" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_incomplete_switch_case_head_statement(self):
|
|
m = self._manager()
|
|
case_blocks = [Block(0x400200 + i, 1, statements=[]) for i in range(2)]
|
|
stmt = IncompleteSwitchCaseHeadStatement(
|
|
m.next_atom(),
|
|
self._vvar(m, 1),
|
|
[
|
|
(case_blocks[0], 0, 0x400300, None, 0x400210),
|
|
(case_blocks[1], 1, 0x400400, None, 0x400220),
|
|
(None, "default", 0x400500, None, 0x400230),
|
|
],
|
|
ins_addr=0x400100,
|
|
)
|
|
|
|
out = _render(self.codegen._handle(stmt, is_expr=False))
|
|
# every case target has to survive, plus the default
|
|
assert "0x400300" in out
|
|
assert "0x400400" in out
|
|
assert "0x400500" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_incomplete_switch_case_node(self):
|
|
m = self._manager()
|
|
head = Block(
|
|
0x400100, 1, statements=[Jump(m.next_atom(), Const(m.next_atom(), 0x400200, 64), ins_addr=0x400100)]
|
|
)
|
|
cases = [
|
|
SequenceNode(
|
|
0x400200,
|
|
nodes=[
|
|
Block(
|
|
0x400200,
|
|
1,
|
|
statements=[Jump(m.next_atom(), Const(m.next_atom(), 0x400300, 64), ins_addr=0x400200)],
|
|
)
|
|
],
|
|
)
|
|
]
|
|
node = IncompleteSwitchCaseNode(0x400100, head, cases)
|
|
|
|
out = _render(self.codegen._handle(node, is_expr=False))
|
|
assert "incomplete" in out
|
|
assert "0x400200" in out
|
|
assert PLACEHOLDER not in out
|
|
|
|
def test_bbbq_rust_flavor_has_no_placeholders(self):
|
|
bin_path = os.path.join(test_location, "x86_64", "bbbq")
|
|
proj, cfg = load_project_with_scoped_cfg(bin_path, 0x410920, expand_call_tree=False, run_ccc=False)
|
|
proj.analyses.RustSymbolRecovery()
|
|
proj.analyses.TypeDBLoader()
|
|
dec = proj.analyses.Decompiler(0x410920, cfg=cfg.model, flavor="rust", fail_fast=True)
|
|
assert dec.codegen is not None and dec.codegen.text is not None
|
|
print_decompilation_result(dec)
|
|
|
|
text = dec.codegen.text
|
|
assert PLACEHOLDER not in text
|
|
# the bit-insertions that used to be dropped are rendered now
|
|
assert "_INSERT(" in text
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|