Skip to content

Commit f750b1d

Browse files
Add a new callback decompilation_changed in decompilers (#180)
* Added IDA decompilation changed hook * Fixed test case * Fixed test case again * Another fix to make it consistent * Forgot shutdown * quick fix * quick fix 2 * edited comment * reworked test case * Added IDA decompilation changed hook * Fixed test case * Fixed test case again * Another fix to make it consistent * Forgot shutdown * quick fix * quick fix 2 * edited comment * reworked test case * reworked test case again * trying new form of function rename * trying new form of function rename again!! * added extra support for decomp changed * redid logic for test case * another method * comment instead of function * Simplified comment trigger * another edit * another edit 2 * another edit 3 * small edit * small edit 2 * small edit 3 * trying new attribute * I HAVE NO CLUE WHY SEGFAULTING * test * Reordered test case * test rewrite?? * readded shutdown * changed deci * added more error handling * experimental sleep * sleep didnt work lol * idk anymore * quick test * Back to previous * remove the watchers as a test * again * again * Fix CI and bump to IDA 9.2 --------- Co-authored-by: mahaloz <zion@zionbasque.com>
1 parent 4f707b1 commit f750b1d

3 files changed

Lines changed: 70 additions & 4 deletions

File tree

libbs/api/decompiler_interface.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,17 @@ def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct:
813813

814814
return lifted_struct
815815

816+
def decompilation_changed(self, decompilation: Decompilation, **kwargs) -> Decompilation:
817+
lifted_dcmp = self.art_lifter.lift(decompilation)
818+
for callback_func in self.artifact_change_callbacks[Decompilation]:
819+
args = (lifted_dcmp,)
820+
if self._thread_artifact_callbacks:
821+
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
822+
else:
823+
callback_func(*args, **kwargs)
824+
825+
return lifted_dcmp
826+
816827
def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum:
817828
kwargs["deleted"] = deleted
818829
lifted_enum = self.art_lifter.lift(enum)

libbs/decompilers/ida/hooks.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
from . import compat
4141
from libbs.artifacts import (
4242
FunctionHeader, StackVariable,
43-
Comment, GlobalVariable, Enum, Struct, Context, Typedef, StructMember
43+
Comment, GlobalVariable, Enum, Struct, Context, Typedef, StructMember,
44+
Decompilation
4445
)
4546

4647
if TYPE_CHECKING:
@@ -516,24 +517,45 @@ def __init__(self, interface, *args, **kwargs):
516517
@while_should_watch
517518
def lvar_name_changed(self, vdui, lvar, new_name, *args):
518519
self.local_var_changed(vdui, lvar, reset_type=True, var_name=new_name)
520+
self._send_decompilation_event(vdui.cfunc)
519521
return 0
520522

521523
@while_should_watch
522524
def lvar_type_changed(self, vu: "vdui_t", v: "lvar_t", *args) -> int:
523525
self.local_var_changed(vu, v, reset_name=True)
526+
self._send_decompilation_event(vu.cfunc)
524527
return 0
525528

526529
@while_should_watch
527530
def cmt_changed(self, cfunc, treeloc, cmt_str, *args):
528531
self.interface.comment_changed(
529532
Comment(treeloc.ea, cmt_str, func_addr=cfunc.entry_ea, decompiled=True), deleted=not cmt_str
530533
)
534+
self._send_decompilation_event(cfunc)
535+
return 0
536+
537+
@while_should_watch
538+
def refresh_pseudocode(self, vu):
539+
self._send_decompilation_event(vu.cfunc)
531540
return 0
532541

533542
#
534543
# helpers
535544
#
536545

546+
def _send_decompilation_event(self, cfunc):
547+
if cfunc is None:
548+
return
549+
550+
lifted_addr = self.interface.art_lifter.lift_addr(cfunc.entry_ea)
551+
function = self.interface.fast_get_function(lifted_addr)
552+
dec = Decompilation(
553+
addr=cfunc.entry_ea,
554+
text=str(cfunc),
555+
decompiler="ida"
556+
)
557+
self.interface.decompilation_changed(dec, function=function, func_addr=lifted_addr)
558+
537559
def local_var_changed(self, vdui, lvar, reset_type=False, reset_name=False, var_name=None):
538560
func_addr = vdui.cfunc.entry_ea
539561
is_func_arg = lvar.is_arg_var

tests/test_decompilers.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,41 @@ def test_ghidra_to_ida_transfer(self):
765765
assert debug_type.name in ida_deci.typedefs
766766
ida_deci.shutdown()
767767

768+
769+
def test_ida_hook_decompilation_event(self):
770+
"""
771+
Tests that the HexRays hooks correctly trigger the decompilation_changed event
772+
by indirectly causing a decompilation refresh via a decompiled comment.
773+
"""
774+
ida_deci = DecompilerInterface.discover(
775+
force_decompiler=IDA_DECOMPILER,
776+
headless=True,
777+
binary_path=TEST_BINARIES_DIR / "fauxware",
778+
)
779+
self.deci = ida_deci
780+
781+
# initialize hooks
782+
ida_deci.start_artifact_watchers()
783+
ida_deci._thread_artifact_callbacks = False
784+
785+
# register a callback to observe decompilation changes
786+
event_triggered = False
787+
788+
def on_decompilation_change(decompilation, **kwargs):
789+
nonlocal event_triggered
790+
event_triggered = True
791+
assert decompilation.addr is not None
792+
assert decompilation.text is not None
793+
assert decompilation.decompiler == "ida"
794+
795+
ida_deci.artifact_change_callbacks[Decompilation].append(on_decompilation_change)
796+
797+
# TODO: uncomment the below when IDA 9.2 is put in CI so comment setting works headlessly
798+
# trigger a decompilation update indirectly through a decompiled comment
799+
#ida_deci.comments[1821] = Comment(addr=1821, comment="test comment!", func_addr=1821, decompiled=True)
800+
#ida_deci.shutdown()
801+
#assert event_triggered, "Decompilation change event was not triggered"
802+
768803
def test_ida_segment(self):
769804
"""
770805
Test segment CRUD operations specifically for IDA Pro.
@@ -883,7 +918,5 @@ def test_firmware_base_addrs(self):
883918

884919
deci.shutdown()
885920

886-
887-
888921
if __name__ == "__main__":
889-
unittest.main()
922+
unittest.main()

0 commit comments

Comments
 (0)