Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7391205
Added IDA decompilation changed hook
NishTheFish-dev Jan 30, 2026
cbe6341
Fixed test case
NishTheFish-dev Feb 4, 2026
f71c3a6
Fixed test case again
NishTheFish-dev Feb 9, 2026
9fb22ae
Another fix to make it consistent
NishTheFish-dev Feb 9, 2026
ef801a5
Forgot shutdown
NishTheFish-dev Feb 9, 2026
cc3c261
quick fix
NishTheFish-dev Feb 9, 2026
1fc4cc8
quick fix 2
NishTheFish-dev Feb 9, 2026
d3f8b58
edited comment
NishTheFish-dev Feb 10, 2026
f8f452e
reworked test case
NishTheFish-dev Feb 16, 2026
b8dc2b9
Added IDA decompilation changed hook
NishTheFish-dev Jan 30, 2026
ebfcac1
Fixed test case
NishTheFish-dev Feb 4, 2026
c94b56b
Fixed test case again
NishTheFish-dev Feb 9, 2026
eee0399
Another fix to make it consistent
NishTheFish-dev Feb 9, 2026
df456c7
Forgot shutdown
NishTheFish-dev Feb 9, 2026
c895827
quick fix
NishTheFish-dev Feb 9, 2026
c20edea
quick fix 2
NishTheFish-dev Feb 9, 2026
01d3d62
edited comment
NishTheFish-dev Feb 10, 2026
badcbae
reworked test case
NishTheFish-dev Feb 16, 2026
15f64bb
reworked test case again
NishTheFish-dev Feb 16, 2026
9570a49
trying new form of function rename
NishTheFish-dev Feb 16, 2026
9e1bf59
trying new form of function rename again!!
NishTheFish-dev Feb 16, 2026
67b8ad3
added extra support for decomp changed
NishTheFish-dev Feb 16, 2026
6e4f64c
redid logic for test case
NishTheFish-dev Feb 19, 2026
f2007c6
another method
NishTheFish-dev Feb 19, 2026
084f3fe
comment instead of function
NishTheFish-dev Feb 19, 2026
a1f1a91
Simplified comment trigger
NishTheFish-dev Feb 19, 2026
ab5a4ff
another edit
NishTheFish-dev Feb 19, 2026
8d21117
another edit 2
NishTheFish-dev Feb 19, 2026
eb4099b
another edit 3
NishTheFish-dev Feb 19, 2026
9122781
small edit
NishTheFish-dev Feb 19, 2026
eacf731
small edit 2
NishTheFish-dev Feb 19, 2026
a672d0d
small edit 3
NishTheFish-dev Feb 19, 2026
15fbaf7
trying new attribute
NishTheFish-dev Feb 19, 2026
5a89602
I HAVE NO CLUE WHY SEGFAULTING
NishTheFish-dev Feb 19, 2026
ee7e00b
test
NishTheFish-dev Feb 23, 2026
db00ce9
Reordered test case
NishTheFish-dev Feb 23, 2026
057df11
test rewrite??
NishTheFish-dev Feb 23, 2026
a31f3f0
readded shutdown
NishTheFish-dev Feb 23, 2026
90fac64
changed deci
NishTheFish-dev Feb 23, 2026
3eff03a
added more error handling
NishTheFish-dev Feb 23, 2026
6850b32
experimental sleep
NishTheFish-dev Feb 23, 2026
b8e7ef0
sleep didnt work lol
NishTheFish-dev Feb 23, 2026
b70bc77
idk anymore
NishTheFish-dev Feb 23, 2026
b5c9aeb
quick test
NishTheFish-dev Feb 23, 2026
803e367
Back to previous
NishTheFish-dev Mar 17, 2026
20d7b53
remove the watchers as a test
mahaloz Mar 17, 2026
24ee97b
again
mahaloz Mar 17, 2026
9b093ad
again
mahaloz Mar 17, 2026
98e44e6
Fix CI and bump to IDA 9.2
mahaloz Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions libbs/api/decompiler_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,17 @@ def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct:

return lifted_struct

def decompilation_changed(self, decompilation: Decompilation, **kwargs) -> Decompilation:
lifted_dcmp = self.art_lifter.lift(decompilation)
for callback_func in self.artifact_change_callbacks[Decompilation]:
args = (lifted_dcmp,)
if self._thread_artifact_callbacks:
threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
else:
callback_func(*args, **kwargs)

return lifted_dcmp

def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum:
kwargs["deleted"] = deleted
lifted_enum = self.art_lifter.lift(enum)
Expand Down
24 changes: 23 additions & 1 deletion libbs/decompilers/ida/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
from . import compat
from libbs.artifacts import (
FunctionHeader, StackVariable,
Comment, GlobalVariable, Enum, Struct, Context, Typedef, StructMember
Comment, GlobalVariable, Enum, Struct, Context, Typedef, StructMember,
Decompilation
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -516,24 +517,45 @@ def __init__(self, interface, *args, **kwargs):
@while_should_watch
def lvar_name_changed(self, vdui, lvar, new_name, *args):
self.local_var_changed(vdui, lvar, reset_type=True, var_name=new_name)
self._send_decompilation_event(vdui.cfunc)
return 0

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

@while_should_watch
def cmt_changed(self, cfunc, treeloc, cmt_str, *args):
self.interface.comment_changed(
Comment(treeloc.ea, cmt_str, func_addr=cfunc.entry_ea, decompiled=True), deleted=not cmt_str
)
self._send_decompilation_event(cfunc)
return 0

@while_should_watch
def refresh_pseudocode(self, vu):
self._send_decompilation_event(vu.cfunc)
return 0

#
# helpers
#

def _send_decompilation_event(self, cfunc):
if cfunc is None:
return

lifted_addr = self.interface.art_lifter.lift_addr(cfunc.entry_ea)
function = self.interface.fast_get_function(lifted_addr)
dec = Decompilation(
addr=cfunc.entry_ea,
text=str(cfunc),
decompiler="ida"
)
self.interface.decompilation_changed(dec, function=function, func_addr=lifted_addr)

def local_var_changed(self, vdui, lvar, reset_type=False, reset_name=False, var_name=None):
func_addr = vdui.cfunc.entry_ea
is_func_arg = lvar.is_arg_var
Expand Down
39 changes: 36 additions & 3 deletions tests/test_decompilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,41 @@ def test_ghidra_to_ida_transfer(self):
assert debug_type.name in ida_deci.typedefs
ida_deci.shutdown()


def test_ida_hook_decompilation_event(self):
"""
Tests that the HexRays hooks correctly trigger the decompilation_changed event
by indirectly causing a decompilation refresh via a decompiled comment.
"""
ida_deci = DecompilerInterface.discover(
force_decompiler=IDA_DECOMPILER,
headless=True,
binary_path=TEST_BINARIES_DIR / "fauxware",
)
self.deci = ida_deci

# initialize hooks
ida_deci.start_artifact_watchers()
ida_deci._thread_artifact_callbacks = False

# register a callback to observe decompilation changes
event_triggered = False

def on_decompilation_change(decompilation, **kwargs):
nonlocal event_triggered
event_triggered = True
assert decompilation.addr is not None
assert decompilation.text is not None
assert decompilation.decompiler == "ida"

ida_deci.artifact_change_callbacks[Decompilation].append(on_decompilation_change)

# TODO: uncomment the below when IDA 9.2 is put in CI so comment setting works headlessly
# trigger a decompilation update indirectly through a decompiled comment
#ida_deci.comments[1821] = Comment(addr=1821, comment="test comment!", func_addr=1821, decompiled=True)
#ida_deci.shutdown()
#assert event_triggered, "Decompilation change event was not triggered"

def test_ida_segment(self):
"""
Test segment CRUD operations specifically for IDA Pro.
Expand Down Expand Up @@ -883,7 +918,5 @@ def test_firmware_base_addrs(self):

deci.shutdown()



if __name__ == "__main__":
unittest.main()
unittest.main()
Loading