Skip to content

Commit 3369388

Browse files
committed
Python: Add MLIL to copy_expr test workflow
1 parent 2490182 commit 3369388

File tree

1 file changed

+118
-1
lines changed

1 file changed

+118
-1
lines changed

python/examples/wf_test_copy_expr.py

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import json
2+
import math
3+
24
from binaryninja import Workflow, Activity, AnalysisContext, ReportCollection, \
35
FlowGraphReport, show_report_collection, DisassemblySettings, DisassemblyOption
46
from binaryninja.lowlevelil import *
7+
from binaryninja.mediumlevelil import *
58

69
"""
710
This workflow copies every instruction in an IL function to a new IL function and then
@@ -20,7 +23,7 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
2023
"""
2124
err_msg = (hex(old_insn.address), old_insn, new_insn)
2225
assert old_insn.operation == new_insn.operation, err_msg
23-
assert old_insn.attributes == new_insn.attributes, err_msg
26+
# assert old_insn.attributes == new_insn.attributes, err_msg
2427
assert old_insn.size == new_insn.size, err_msg
2528
assert old_insn.raw_flags == new_insn.raw_flags, err_msg
2629
assert old_insn.source_location == new_insn.source_location, err_msg
@@ -48,6 +51,12 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
4851
]:
4952
for old_sub, new_sub in zip(old_op[1], new_op[1]):
5053
assert_llil_eq(old_sub, new_sub)
54+
elif op_type == 'float':
55+
if math.isnan(old_op[1]) and math.isnan(new_op[1]):
56+
# both nan so they will compare not equal
57+
pass
58+
else:
59+
assert old_op[1] == new_op[1], err_msg
5160
elif old_insn.operation == LowLevelILOperation.LLIL_JUMP_TO and old_op[0] == 'targets':
5261
for old_target, new_target in zip(sorted(old_op[1].items()), sorted(new_op[1].items())):
5362
assert old_target[0] == new_target[0], err_msg
@@ -58,6 +67,67 @@ def assert_llil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruct
5867
assert old_op[1] == new_op[1], err_msg
5968

6069

70+
def assert_mlil_eq(old_insn: LowLevelILInstruction, new_insn: LowLevelILInstruction):
71+
"""
72+
Make sure that these two instructions are the same (probably correct). Asserts otherwise.
73+
74+
Note: This ignores when instructions reference other instructions by index directly
75+
as that IL indices are not guaranteed to be consistent. So things like goto/if/jump_to
76+
will check that the target of the branch is the same, but allow the target to have
77+
a different instruction index.
78+
"""
79+
err_msg = (hex(old_insn.address), old_insn, new_insn)
80+
assert old_insn.operation == new_insn.operation, err_msg
81+
# assert old_insn.attributes == new_insn.attributes, err_msg
82+
assert old_insn.size == new_insn.size, err_msg
83+
assert old_insn.source_location == new_insn.source_location, err_msg
84+
assert len(old_insn.operands) == len(new_insn.operands), err_msg
85+
# Can't compare operands directly since IL expression indices might change when
86+
# copying an instruction to another function
87+
for i, (old_op, new_op) in enumerate(zip(old_insn.detailed_operands, new_insn.detailed_operands)):
88+
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op)
89+
assert old_op[0] == new_op[0], err_msg # op name
90+
assert old_op[2] == new_op[2], err_msg # op type
91+
92+
op_type = old_op[2]
93+
if op_type == 'MediumLevelILInstruction':
94+
assert_mlil_eq(old_op[1], new_op[1])
95+
elif op_type == 'InstructionIndex' or \
96+
(old_insn.operation == MediumLevelILOperation.MLIL_GOTO and old_op[0] == 'dest') or \
97+
(old_insn.operation == MediumLevelILOperation.MLIL_IF and old_op[0] == 'true') or \
98+
(old_insn.operation == MediumLevelILOperation.MLIL_IF and old_op[0] == 'false'):
99+
# These aren't consistent if the old function has instructions outside BBs
100+
# (they are not copied), so just make sure the target instruction looks the same
101+
assert old_insn.function[old_op[1]].operation == new_insn.function[new_op[1]].operation
102+
elif op_type == 'List[MediumLevelILInstruction]':
103+
for old_sub, new_sub in zip(old_op[1], new_op[1]):
104+
assert_mlil_eq(old_sub, new_sub)
105+
elif op_type == 'float':
106+
if math.isnan(old_op[1]) and math.isnan(new_op[1]):
107+
# both nan so they will compare not equal
108+
pass
109+
else:
110+
assert old_op[1] == new_op[1], err_msg
111+
elif op_type == 'Variable':
112+
assert old_op[1].core_variable == new_op[1].core_variable, err_msg
113+
elif op_type == 'List[Variable]':
114+
for old_sub, new_sub in zip(old_op[1], new_op[1]):
115+
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op, old_sub, new_sub)
116+
assert old_sub.core_variable == new_sub.core_variable, err_msg
117+
elif op_type == 'SSAVariable':
118+
assert old_op[1].var.core_variable == new_op[1].var.core_variable, err_msg
119+
assert old_op[1].version == new_op[1].version, err_msg
120+
elif old_insn.operation == MediumLevelILOperation.MLIL_JUMP_TO and old_op[0] == 'targets':
121+
for old_target, new_target in zip(sorted(old_op[1].items()), sorted(new_op[1].items())):
122+
err_msg = (hex(old_insn.address), f'op {i}', old_insn, new_insn, old_op, new_op, old_target, new_target)
123+
assert old_target[0] == new_target[0], err_msg
124+
# Same as with instruction index
125+
assert_mlil_eq(old_insn.function[old_target[1]], new_insn.function[new_target[1]])
126+
else:
127+
# TODO: Any other types of ops need special behavior?
128+
assert old_op[1] == new_op[1], err_msg
129+
130+
61131
def lil_action(context: AnalysisContext):
62132
def translate_instr(
63133
new_func: LowLevelILFunction,
@@ -133,6 +203,44 @@ def translate_instr(
133203
assert_llil_eq(old_insn, new_insn)
134204

135205

206+
def mlil_action(context: AnalysisContext):
207+
def translate_instr(
208+
new_func: MediumLevelILFunction,
209+
old_block: MediumLevelILBasicBlock,
210+
old_instr: MediumLevelILInstruction,
211+
):
212+
# no-op copy
213+
return old_instr.copy_to(
214+
new_func,
215+
lambda sub_instr: translate_instr(new_func, old_block, sub_instr)
216+
)
217+
218+
old_mlil = context.mlil
219+
if old_mlil is None:
220+
return
221+
new_mlil = old_mlil.translate(translate_instr)
222+
new_mlil.finalize()
223+
new_mlil.generate_ssa_form()
224+
225+
if context.function.check_for_debug_report("copy_expr_test_mlil"):
226+
# debug the test :)
227+
report = ReportCollection()
228+
settings = DisassemblySettings()
229+
settings.set_option(DisassemblyOption.ShowAddress, True)
230+
report.append(FlowGraphReport("old graph", old_mlil.create_graph_immediate(settings)))
231+
report.append(FlowGraphReport("new graph", new_mlil.create_graph_immediate(settings)))
232+
show_report_collection("copy expr test", report)
233+
234+
# Check all BBs have all the same instructions
235+
# Technically, this misses any instructions outside a BB, but those are not
236+
# picked up by analysis anyway, and therefore don't matter.
237+
assert len(old_mlil.basic_blocks) == len(new_mlil.basic_blocks)
238+
for old_bb, new_bb in zip(old_mlil.basic_blocks, new_mlil.basic_blocks):
239+
assert len(old_bb) == len(new_bb)
240+
for old_insn, new_insn in zip(old_bb, new_bb):
241+
assert_mlil_eq(old_insn, new_insn)
242+
243+
136244
wf = Workflow("core.function.metaAnalysis").clone("TestCopyExpr")
137245

138246
# Define the custom activity configuration
@@ -152,7 +260,16 @@ def translate_instr(
152260
}),
153261
action=llil_action
154262
))
263+
wf.register_activity(Activity(
264+
configuration=json.dumps({
265+
"name": "extension.test_copy_expr.mlil_action",
266+
"title": "Medium Level IL copy_expr Test",
267+
"description": "Makes sure copy_expr works on Medium Level IL functions."
268+
}),
269+
action=mlil_action
270+
))
155271
wf.insert("core.function.analyzeAndExpandFlags", ["extension.test_copy_expr.lil_action"])
156272
wf.insert("core.function.generateMediumLevelIL", ["extension.test_copy_expr.llil_action"])
273+
wf.insert("core.function.generateHighLevelIL", ["extension.test_copy_expr.mlil_action"])
157274
# TODO: MLIL and higher
158275
wf.register()

0 commit comments

Comments
 (0)