Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
be6a19c
Added IDA decompilation changed hook
NishTheFish-dev Jan 30, 2026
c0530b9
Fixed test case
NishTheFish-dev Feb 4, 2026
41a80ac
Fixed test case again
NishTheFish-dev Feb 9, 2026
61a92a3
Another fix to make it consistent
NishTheFish-dev Feb 9, 2026
2807aff
Forgot shutdown
NishTheFish-dev Feb 9, 2026
7c717d8
quick fix
NishTheFish-dev Feb 9, 2026
fec04e1
quick fix 2
NishTheFish-dev Feb 9, 2026
f4d070a
edited comment
NishTheFish-dev Feb 10, 2026
5e2b99d
reworked test case
NishTheFish-dev Feb 16, 2026
2932db6
Added IDA decompilation changed hook
NishTheFish-dev Jan 30, 2026
f4342a7
Fixed test case
NishTheFish-dev Feb 4, 2026
ec2d425
Fixed test case again
NishTheFish-dev Feb 9, 2026
4104072
Another fix to make it consistent
NishTheFish-dev Feb 9, 2026
7308e4e
Forgot shutdown
NishTheFish-dev Feb 9, 2026
e8965cb
quick fix
NishTheFish-dev Feb 9, 2026
9bfe033
quick fix 2
NishTheFish-dev Feb 9, 2026
a11eeb5
edited comment
NishTheFish-dev Feb 10, 2026
c231021
reworked test case
NishTheFish-dev Feb 16, 2026
dac0054
Merge branch 'feat/new_dcmpchanged_callback' of https://github.com/bi…
NishTheFish-dev Feb 16, 2026
137fc2d
reworked test case again
NishTheFish-dev Feb 16, 2026
68b800e
trying new form of function rename
NishTheFish-dev Feb 16, 2026
112d946
trying new form of function rename again!!
NishTheFish-dev Feb 16, 2026
285941a
added extra support for decomp changed
NishTheFish-dev Feb 16, 2026
fae4d09
redid logic for test case
NishTheFish-dev Feb 19, 2026
c1bff91
another method
NishTheFish-dev Feb 19, 2026
05127ed
comment instead of function
NishTheFish-dev Feb 19, 2026
55799c7
Simplified comment trigger
NishTheFish-dev Feb 19, 2026
0342f23
another edit
NishTheFish-dev Feb 19, 2026
4370611
another edit 2
NishTheFish-dev Feb 19, 2026
fcb5e0d
another edit 3
NishTheFish-dev Feb 19, 2026
314c700
small edit
NishTheFish-dev Feb 19, 2026
ebbdb0a
small edit 2
NishTheFish-dev Feb 19, 2026
b64e2ce
small edit 3
NishTheFish-dev Feb 19, 2026
9f17bc8
trying new attribute
NishTheFish-dev Feb 19, 2026
260906a
I HAVE NO CLUE WHY SEGFAULTING
NishTheFish-dev Feb 19, 2026
5144cff
test
NishTheFish-dev Feb 23, 2026
6c8900a
Reordered test case
NishTheFish-dev Feb 23, 2026
540c4fb
test rewrite??
NishTheFish-dev Feb 23, 2026
af2b957
readded shutdown
NishTheFish-dev Feb 23, 2026
57ea3e1
changed deci
NishTheFish-dev Feb 23, 2026
3f7b7ef
added more error handling
NishTheFish-dev Feb 23, 2026
7955e4f
experimental sleep
NishTheFish-dev Feb 23, 2026
d9de6c5
sleep didnt work lol
NishTheFish-dev Feb 23, 2026
4bfd9ba
idk anymore
NishTheFish-dev Feb 23, 2026
07ddb04
quick test
NishTheFish-dev Feb 23, 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
37 changes: 34 additions & 3 deletions tests/test_decompilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,39 @@ 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)

# trigger a decompilation update indirectly through a decompiled comment
ida_deci.comments[1821] = Comment(addr=1821, comment="test comment!", func_addr=1821, decompiled=True)
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 +916,5 @@ def test_firmware_base_addrs(self):

deci.shutdown()



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