diff --git a/examples/change_watcher_plugin/bs_change_watcher/__init__.py b/examples/change_watcher_plugin/bs_change_watcher/__init__.py index 04a6729..3598a00 100644 --- a/examples/change_watcher_plugin/bs_change_watcher/__init__.py +++ b/examples/change_watcher_plugin/bs_change_watcher/__init__.py @@ -22,8 +22,10 @@ def create_plugin(*args, **kwargs): plugin_name="ArtifactChangeWatcher", init_plugin=True, decompiler_started_callbacks=decompiler_started_event_callbacks, + # passing the flag below forces click recording to start on decompiler startup + # force_click_recording = True, gui_init_args=args, - gui_init_kwargs=kwargs + gui_init_kwargs=kwargs, ) # create a function to print a string in the decompiler console decompiler_printer = lambda *x, **y: deci.print(f"Changed {x}") diff --git a/libbs/__init__.py b/libbs/__init__.py index 1d31fba..ef4f299 100644 --- a/libbs/__init__.py +++ b/libbs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.3.1" +__version__ = "2.4.0" import logging diff --git a/libbs/api/decompiler_interface.py b/libbs/api/decompiler_interface.py index 26d2678..fb93434 100644 --- a/libbs/api/decompiler_interface.py +++ b/libbs/api/decompiler_interface.py @@ -64,6 +64,7 @@ def __init__( undo_event_callbacks: Optional[List[Callable]] = None, decompiler_started_callbacks: Optional[List[Callable]] = None, thread_artifact_callbacks: bool = True, + force_click_recording: bool = False, ): self.name = name self.art_lifter = artifact_lifter @@ -82,7 +83,8 @@ def __init__( self._gui_ctx_menu_actions = [] self._plugin_name = plugin_name self.gui_plugin = None - self._artifact_watchers_started = False + self.artifact_watchers_started = False + self.force_click_recording = force_click_recording # locks self.artifact_write_lock = threading.Lock() @@ -155,7 +157,7 @@ def _init_gui_plugin(self, *args, **kwargs): def shutdown(self): self.config.save() - if self._artifact_watchers_started: + if self.artifact_watchers_started: self.stop_artifact_watchers() # @@ -236,7 +238,7 @@ def start_artifact_watchers(self): @return: """ self.debug("Starting BinSync artifact watchers...") - self._artifact_watchers_started = True + self.artifact_watchers_started = True def stop_artifact_watchers(self): """ @@ -246,7 +248,7 @@ def stop_artifact_watchers(self): react to them. """ self.debug("Stopping BinSync artifact watchers...") - self._artifact_watchers_started = False + self.artifact_watchers_started = False @property def binary_base_addr(self) -> int: diff --git a/libbs/decompilers/angr/interface.py b/libbs/decompilers/angr/interface.py index ffe2a9f..7d23483 100644 --- a/libbs/decompilers/angr/interface.py +++ b/libbs/decompilers/angr/interface.py @@ -402,12 +402,12 @@ def _angr_management_decompile(self, func): def decompile_function(self, func, refresh_gui=False): # check for known decompilation - available = self.main_instance.project.kb.structured_code.available_flavors(func.addr) + available = self.main_instance.project.kb.decompilations.available_flavors(func.addr) should_decompile = False if self.headless or 'pseudocode' not in available: should_decompile = True else: - cached = self.main_instance.project.kb.structured_code[(func.addr, 'pseudocode')] + cached = self.main_instance.project.kb.decompilations[(func.addr, 'pseudocode')] if isinstance(cached, DummyStructuredCodeGenerator): should_decompile = True @@ -420,7 +420,7 @@ def decompile_function(self, func, refresh_gui=False): # grab newly cached pseudocode if not self.headless: - decomp = self.main_instance.project.kb.structured_code[(func.addr, 'pseudocode')] + decomp = self.main_instance.project.kb.decompilations[(func.addr, 'pseudocode')] # refresh the UI after decompiling if refresh_gui and not self.headless: diff --git a/libbs/decompilers/binja/interface.py b/libbs/decompilers/binja/interface.py index 0dad65e..4b7aea4 100644 --- a/libbs/decompilers/binja/interface.py +++ b/libbs/decompilers/binja/interface.py @@ -298,7 +298,7 @@ def get_decompilation_object(self, function: Function, **kwargs) -> Optional[obj return None def start_artifact_watchers(self): - if not self._artifact_watchers_started: + if not self.artifact_watchers_started: from .hooks import DataMonitor if self.bv is None: raise RuntimeError("Cannot start artifact watchers without a BinaryView.") @@ -308,7 +308,7 @@ def start_artifact_watchers(self): super().start_artifact_watchers() def stop_artifact_watchers(self): - if self._artifact_watchers_started: + if self.artifact_watchers_started: self.bv.unregister_notification(self._data_monitor) self._data_monitor = None super().stop_artifact_watchers() diff --git a/libbs/decompilers/ghidra/interface.py b/libbs/decompilers/ghidra/interface.py index e8c6c19..ffe4072 100644 --- a/libbs/decompilers/ghidra/interface.py +++ b/libbs/decompilers/ghidra/interface.py @@ -136,7 +136,7 @@ def start_artifact_watchers(self): return from .hooks import create_data_monitor - if not self._artifact_watchers_started: + if not self.artifact_watchers_started: if self.flat_api is None: raise RuntimeError("Cannot start artifact watchers without Ghidra Bridge connection.") @@ -145,7 +145,7 @@ def start_artifact_watchers(self): super().start_artifact_watchers() def stop_artifact_watchers(self): - if self._artifact_watchers_started: + if self.artifact_watchers_started: self._data_monitor = None # TODO: generalize superclass method? super().stop_artifact_watchers() diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index 52de3a9..cf97791 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -171,10 +171,15 @@ def set_func_ret_type(ea, return_type_str): # +@execute_write +def _get_ida_version(): + return idaapi.get_kernel_version() + + def get_ida_version(): global _IDA_VERSION if _IDA_VERSION is None: - _IDA_VERSION = Version(idaapi.get_kernel_version()) + _IDA_VERSION = Version(_get_ida_version()) return _IDA_VERSION @@ -1627,6 +1632,7 @@ def xrefs_to(addr): return list(idautils.XrefsTo(addr)) +@execute_write def wait_for_idc_initialization(): idc.auto_wait() @@ -1648,11 +1654,13 @@ def has_older_hexrays_version(): return not vers.startswith("8.2") +@execute_write def get_decompiler_version() -> typing.Optional[Version]: wait_for_idc_initialization() try: _vers = ida_hexrays.get_hexrays_version() - except Exception: + except Exception as e: + _l.critical("Failed to get decompiler version: %s", e) return None try: diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index 2576986..ad40476 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -89,29 +89,25 @@ def __init__(self, interface: "IDAInterface"): super(ScreenHook, self).__init__() def view_click(self, view, event): - if not self.interface._artifact_watchers_started: - return - - ctx = compat.view_to_bs_context(view) - if ctx is None: - return - - ctx = self.interface.art_lifter.lift(ctx) - self.interface._gui_active_context = ctx - self.interface.gui_context_changed(ctx) + self._handle_view_event(view, click=True) def view_activated(self, view: "TWidget *"): - if not self.interface._artifact_watchers_started: - return + self._handle_view_event(view) - ctx = compat.view_to_bs_context(view) - if ctx is None: - return + def _handle_view_event(self, view, click=False): + if self.interface.force_click_recording or self.interface.artifact_watchers_started: + # drop ctx for speed when the artifact watches have not been officially started, and we are not clicking + if (self.interface.force_click_recording and not self.interface.artifact_watchers_started) and not click: + return + + ctx = compat.view_to_bs_context(view) + if ctx is None: + return - ctx = self.interface.art_lifter.lift(ctx) - self.interface._gui_active_context = ctx - self.interface.gui_context_changed(ctx) + ctx = self.interface.art_lifter.lift(ctx) + self.interface._gui_active_context = ctx + self.interface.gui_context_changed(ctx) class IDAHotkeyHook(ida_kernwin.UI_Hooks): def __init__(self, keys_to_pass, uiptr): diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index 16a716d..041e63d 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -257,7 +257,7 @@ def should_watch_artifacts(self) -> bool: if not self._ida_analysis_finished: self._ida_analysis_finished = ida_auto.auto_is_ok() - return self._ida_analysis_finished and self._artifact_watchers_started + return self._ida_analysis_finished and self.artifact_watchers_started # # Optional API