From 36a5665b16ecd1316ec8343cda0eb41e6ee9b3cc Mon Sep 17 00:00:00 2001 From: Zion Leonahenahe Basque Date: Sun, 21 Jul 2024 16:51:10 -0700 Subject: [PATCH] Feature: support fine-grained Context callbacks (#88) * Add support for context callbacks * Feat: Support fine-grained contexts * Make it work for BinSync again * Everything working in BinSync * bump --- .../bs_change_watcher/__init__.py | 10 ++-- libbs/__init__.py | 2 +- libbs/api/artifact_lifter.py | 2 +- libbs/api/decompiler_interface.py | 51 +++++++++++++------ libbs/artifacts/__init__.py | 1 + libbs/artifacts/context.py | 26 ++++++++++ libbs/decompilers/angr/interface.py | 24 ++++++--- libbs/decompilers/binja/interface.py | 27 ++++++---- libbs/decompilers/ghidra/interface.py | 40 +++++++++++---- libbs/decompilers/ida/compat.py | 12 +++++ libbs/decompilers/ida/hooks.py | 37 ++++++++++---- libbs/decompilers/ida/interface.py | 51 ++++++++++++------- tests/test_decompilers.py | 16 ++++++ tests/test_remote_ghidra.py | 2 +- 14 files changed, 224 insertions(+), 77 deletions(-) create mode 100644 libbs/artifacts/context.py diff --git a/examples/change_watcher_plugin/bs_change_watcher/__init__.py b/examples/change_watcher_plugin/bs_change_watcher/__init__.py index ed0d0d40..7e82a6d1 100644 --- a/examples/change_watcher_plugin/bs_change_watcher/__init__.py +++ b/examples/change_watcher_plugin/bs_change_watcher/__init__.py @@ -14,7 +14,7 @@ def create_plugin(*args, **kwargs): from libbs.api import DecompilerInterface from libbs.artifacts import ( - FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment + FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment, Context ) deci = DecompilerInterface.discover( @@ -24,10 +24,12 @@ def create_plugin(*args, **kwargs): gui_init_kwargs=kwargs ) # create a function to print a string in the decompiler console - decompiler_printer = lambda *x: deci.print(f"Changed {x}") + decompiler_printer = lambda *x, **y: deci.print(f"Changed {x}") # register the callback for all the types we want to print - deci.artifact_write_callbacks = { - typ: [decompiler_printer] for typ in (FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment,) + deci.artifact_change_callbacks = { + typ: [decompiler_printer] for typ in ( + FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment, Context + ) } # register a menu to open when you right click on the psuedocode view diff --git a/libbs/__init__.py b/libbs/__init__.py index 81b937ba..317452f4 100644 --- a/libbs/__init__.py +++ b/libbs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.13.0" +__version__ = "1.14.0" import logging diff --git a/libbs/api/artifact_lifter.py b/libbs/api/artifact_lifter.py index 1b663de7..5e2f88ee 100644 --- a/libbs/api/artifact_lifter.py +++ b/libbs/api/artifact_lifter.py @@ -72,7 +72,7 @@ def _lift_or_lower_artifact(self, artifact, mode): for attr in target_attrs: if hasattr(lifted_art, attr): curr_val = getattr(lifted_art, attr) - if not curr_val: + if curr_val is None: continue # special handling for stack variables diff --git a/libbs/api/decompiler_interface.py b/libbs/api/decompiler_interface.py index 5e92e3d4..13739181 100644 --- a/libbs/api/decompiler_interface.py +++ b/libbs/api/decompiler_interface.py @@ -17,7 +17,7 @@ Artifact, Function, FunctionHeader, StackVariable, Comment, GlobalVariable, Patch, - Enum, Struct, FunctionArgument, Decompilation + Enum, Struct, FunctionArgument, Context, Decompilation ) from libbs.decompilers import SUPPORTED_DECOMPILERS, ANGR_DECOMPILER, \ BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER @@ -59,7 +59,7 @@ def __init__( gui_init_args: Optional[Tuple] = None, gui_init_kwargs: Optional[Dict] = None, # [artifact_class] = list(callback_func) - artifact_write_callbacks: Optional[Dict[Type[Artifact], List[Callable]]] = None, + artifact_change_callbacks: Optional[Dict[Type[Artifact], List[Callable]]] = None, thread_artifact_callbacks: bool = True, ): self.name = name @@ -69,11 +69,9 @@ def __init__( self.qt_version = qt_version self._error_on_artifact_duplicates = error_on_artifact_duplicates - # GUI things self.headless = headless self._headless_dec_path = Path(headless_dec_path) if headless_dec_path else None self._binary_path = Path(binary_path) if binary_path else None - self._init_plugin = init_plugin self._unparsed_gui_ctx_actions = gui_ctx_menu_actions or {} # (category, name, action_string, callback_func) @@ -86,7 +84,7 @@ def __init__( self.artifact_write_lock = threading.Lock() # callback functions, keyed by Artifact class - self.artifact_write_callbacks = artifact_write_callbacks or defaultdict(list) + self.artifact_change_callbacks = artifact_change_callbacks or defaultdict(list) self._thread_artifact_callbacks = thread_artifact_callbacks # artifact dict aliases: @@ -165,11 +163,11 @@ def shutdown(self): # GUI API # - def gui_active_context(self) -> libbs.artifacts.Function: + def gui_active_context(self) -> Optional[libbs.artifacts.Context]: """ - Returns a libbs Function. Currently only functions are supported as current contexts. - This function will be called very frequently, so its important that its implementation is fast - and can be done many times in the decompiler. + Returns the active location that the user is currently _clicked_ on in the decompiler. + This is returned as a Context object, which can address and screen naming information dependent + on the decompilers exposed data. """ raise NotImplementedError @@ -255,6 +253,18 @@ def binary_path(self) -> Optional[str]: """ return self._binary_path + def fast_get_function(self, func_addr) -> Optional[Function]: + """ + Attempts to get a light version of the Function at func_addr. + This function implements special logic to be faster than grabbing all light-functions, or grabbing + a decompiled function. Use this API in the case where you may need to get a single functions info + many times in a loop. + + @param func_addr: + @return: + """ + raise NotImplementedError + def get_func_size(self, func_addr) -> int: """ Returns the size of a function @@ -529,9 +539,20 @@ def _set_function_header(self, fheader: FunctionHeader, **kwargs) -> bool: # lift it ONCE inside this function. Each one will return the lifted form, for easier overriding. # + def gui_context_changed(self, ctx: Context, **kwargs) -> libbs.artifacts.Context: + # XXX: should this be lifted? + for callback_func in self.artifact_change_callbacks[Context]: + args = (ctx,) + if self._thread_artifact_callbacks: + threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() + else: + callback_func(*args, **kwargs) + + return ctx + def function_header_changed(self, fheader: FunctionHeader, **kwargs) -> FunctionHeader: lifted_fheader = self.art_lifter.lift(fheader) - for callback_func in self.artifact_write_callbacks[FunctionHeader]: + for callback_func in self.artifact_change_callbacks[FunctionHeader]: args = (lifted_fheader,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() @@ -542,7 +563,7 @@ def function_header_changed(self, fheader: FunctionHeader, **kwargs) -> Function def stack_variable_changed(self, svar: StackVariable, **kwargs) -> StackVariable: lifted_svar = self.art_lifter.lift(svar) - for callback_func in self.artifact_write_callbacks[StackVariable]: + for callback_func in self.artifact_change_callbacks[StackVariable]: args = (lifted_svar,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() @@ -554,7 +575,7 @@ def stack_variable_changed(self, svar: StackVariable, **kwargs) -> StackVariable def comment_changed(self, comment: Comment, deleted=False, **kwargs) -> Comment: kwargs["deleted"] = deleted lifted_cmt = self.art_lifter.lift(comment) - for callback_func in self.artifact_write_callbacks[Comment]: + for callback_func in self.artifact_change_callbacks[Comment]: args = (lifted_cmt,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() @@ -566,7 +587,7 @@ def comment_changed(self, comment: Comment, deleted=False, **kwargs) -> Comment: def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct: kwargs["deleted"] = deleted lifted_struct = self.art_lifter.lift(struct) - for callback_func in self.artifact_write_callbacks[Struct]: + for callback_func in self.artifact_change_callbacks[Struct]: args = (lifted_struct,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() @@ -578,7 +599,7 @@ def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct: def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum: kwargs["deleted"] = deleted lifted_enum = self.art_lifter.lift(enum) - for callback_func in self.artifact_write_callbacks[Enum]: + for callback_func in self.artifact_change_callbacks[Enum]: args = (lifted_enum,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() @@ -589,7 +610,7 @@ def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum: def global_variable_changed(self, gvar: GlobalVariable, **kwargs) -> GlobalVariable: lifted_gvar = self.art_lifter.lift(gvar) - for callback_func in self.artifact_write_callbacks[GlobalVariable]: + for callback_func in self.artifact_change_callbacks[GlobalVariable]: args = (lifted_gvar,) if self._thread_artifact_callbacks: threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start() diff --git a/libbs/artifacts/__init__.py b/libbs/artifacts/__init__.py index 5395053f..1c043373 100644 --- a/libbs/artifacts/__init__.py +++ b/libbs/artifacts/__init__.py @@ -8,6 +8,7 @@ from .patch import Patch from .stack_variable import StackVariable from .struct import Struct, StructMember +from .context import Context ART_NAME_TO_CLS = { Function.__name__: Function, diff --git a/libbs/artifacts/context.py b/libbs/artifacts/context.py new file mode 100644 index 00000000..5feee991 --- /dev/null +++ b/libbs/artifacts/context.py @@ -0,0 +1,26 @@ +from typing import Optional + +from .artifact import Artifact + + +class Context(Artifact): + __slots__ = Artifact.__slots__ + ( + "addr", + "func_addr", + "screen_name" + ) + + def __init__(self, addr: int = None, func_addr: Optional[int] = None, screen_name: str = None, **kwargs): + self.addr: Optional[int] = addr + self.func_addr: Optional[int] = func_addr + self.screen_name: str = screen_name + super().__init__(**kwargs) + + def __str__(self): + post_text = f" screen={self.screen_name}" if self.screen_name else "" + if self.func_addr is not None: + post_text = f"@{hex(self.func_addr)}" + post_text + if self.addr is not None: + post_text = hex(self.addr) + post_text + + return f"" diff --git a/libbs/decompilers/angr/interface.py b/libbs/decompilers/angr/interface.py index bb71ff2f..ffe2a9f1 100644 --- a/libbs/decompilers/angr/interface.py +++ b/libbs/decompilers/angr/interface.py @@ -11,7 +11,7 @@ DecompilerInterface, ) from libbs.artifacts import ( - Function, FunctionHeader, Comment, StackVariable, FunctionArgument, Artifact, Decompilation + Function, FunctionHeader, Comment, StackVariable, FunctionArgument, Artifact, Decompilation, Context ) from .artifact_lifter import AngrArtifactLifter @@ -75,6 +75,18 @@ def binary_path(self) -> Optional[str]: except Exception: return None + def fast_get_function(self, func_addr) -> Optional[Function]: + lowered_addr = self.art_lifter.lower_addr(func_addr) + try: + _func = self.main_instance.project.kb.functions[lowered_addr] + except KeyError: + self.warning(f"Function at {hex(func_addr)} not found.") + return None + + func = Function(addr=_func.addr, size=_func.size, name=_func.name) + func.header.type = _func.prototype.returnty.c_repr() if _func.prototype.returnty else None + return self.art_lifter.lift(func) + def get_func_size(self, func_addr) -> int: func_addr = self.art_lifter.lower_addr(func_addr) try: @@ -186,7 +198,7 @@ def gui_register_ctx_menu(self, name, action_string, callback_func, category=Non self.gui_plugin.context_menu_items = self._ctx_menu_items return True - def gui_active_context(self): + def gui_active_context(self) -> Optional[Context]: curr_view = self.workspace.view_manager.current_tab if not curr_view: return None @@ -196,13 +208,13 @@ def gui_active_context(self): except NotImplementedError: return None + # TODO: support addr and screen_name for Context if func is None or func.am_obj is None: return None - func_addr = self.art_lifter.lift_addr(func.addr) - return Function( - func_addr, func.size, header=FunctionHeader(func.name, func_addr) - ) + context = Context(addr=None, func_addr=func.addr) + return self.art_lifter.lift(context) + # # Artifact API diff --git a/libbs/decompilers/binja/interface.py b/libbs/decompilers/binja/interface.py index e0bf6ad5..6d6f120c 100644 --- a/libbs/decompilers/binja/interface.py +++ b/libbs/decompilers/binja/interface.py @@ -32,7 +32,7 @@ from libbs.artifacts import ( Function, FunctionHeader, StackVariable, Comment, GlobalVariable, Patch, StructMember, FunctionArgument, - Enum, Struct, Artifact, Decompilation + Enum, Struct, Artifact, Decompilation, Context ) from .artifact_lifter import BinjaArtifactLifter @@ -92,7 +92,7 @@ def __del__(self): # GUI # - def gui_active_context(self): + def gui_active_context(self) -> Optional[Context]: all_contexts = UIContext.allContexts() if not all_contexts: return None @@ -103,14 +103,14 @@ def gui_active_context(self): return None actionContext = handler.actionContext() - func = actionContext.function - if func is None: + if actionContext is None: return None - func_addr = self.art_lifter.lift_addr(func.start) - return libbs.artifacts.Function( - func_addr, 0, header=FunctionHeader(func.name, func_addr) - ) + func_addr = actionContext.function.start if actionContext.function is not None else None + addr = actionContext.address if actionContext.address is not None else None + # TODO: support screen_name + context = Context(addr=addr, func_addr=func_addr) + return self.art_lifter.lift(context) def gui_goto(self, func_addr) -> None: func_addr = self.art_lifter.lower_addr(func_addr) @@ -163,6 +163,14 @@ def binary_path(self) -> Optional[str]: except Exception: return None + def fast_get_function(self, func_addr) -> Optional[Function]: + func_addr = self.art_lifter.lower_addr(func_addr) + func = self.bv.get_function_at(func_addr) + if not func: + return None + + return self.art_lifter.lift(self.bn_func_to_bs(func)) + def get_func_size(self, func_addr) -> int: func_addr = self.art_lifter.lower_addr(func_addr) func = self.bv.get_function_at(func_addr) @@ -283,7 +291,7 @@ def rename_local_variables_by_names(self, func: Function, name_map: Dict[str, st return update - def get_decompilation_object(self, function: Function) -> Optional[object]: + def get_decompilation_object(self, function: Function, **kwargs) -> Optional[object]: """ Binary Ninja has no internal object that needs to be refreshed. """ @@ -372,6 +380,7 @@ def _set_function_header(self, fheader: FunctionHeader, bn_func=None, **kwargs) if bs_var.type and bs_var.type != self.art_lifter.lift_type(str(bn_var.type)): bn_var.type = bs_var.type updates |= True + # refresh bn_var = bn_func.parameter_vars[i] # name diff --git a/libbs/decompilers/ghidra/interface.py b/libbs/decompilers/ghidra/interface.py index 1768fe3d..0a5fd2a1 100644 --- a/libbs/decompilers/ghidra/interface.py +++ b/libbs/decompilers/ghidra/interface.py @@ -13,7 +13,7 @@ from libbs.api.decompiler_interface import requires_decompilation from libbs.artifacts import ( Function, FunctionHeader, StackVariable, Comment, FunctionArgument, GlobalVariable, Struct, StructMember, Enum, - Decompilation + Decompilation, Context ) from .artifact_lifter import GhidraArtifactLifter @@ -53,8 +53,7 @@ def __init__( self._bridge = None # cachable attributes - self._last_addr = None - self._last_func = None + self._active_ctx = None self._binary_base_addr = None self._default_pointer_size = None @@ -192,17 +191,19 @@ def gui_ask_for_choice(self, question: str, choices: list, title="Plugin Questio ) return answer if answer else "" - def gui_active_context(self): + def gui_active_context(self) -> Optional[Context]: active_addr = self.flat_api.currentLocation.getAddress().getOffset() - if active_addr is None: - return Function(0, 0) + if (self._active_ctx is None) or (active_addr is not None and self._active_ctx.addr != active_addr): + gfuncs = self.__fast_function(active_addr) + gfunc = gfuncs[0] if gfuncs else None + # TODO: support scree_name + context = Context(addr=active_addr) + if gfunc is not None: + context.func_addr = int(gfunc.getEntryPoint().getOffset()) - if active_addr != self._last_addr: - self._last_addr = active_addr - self._last_func = self._gfunc_to_bsfunc(self._get_nearest_function(active_addr)) - self._last_func.addr = self.art_lifter.lift_addr(self._last_func.addr) + self._active_ctx = self.art_lifter.lift(context) - return self._last_func + return self._active_ctx def gui_goto(self, func_addr) -> None: func_addr = self.art_lifter.lower_addr(func_addr) @@ -212,6 +213,17 @@ def gui_goto(self, func_addr) -> None: # Mandatory API # + def fast_get_function(self, func_addr) -> Optional[Function]: + lowered_addr = self.art_lifter.lower_addr(func_addr) + gfuncs = self.__fast_function(lowered_addr) + gfunc = gfuncs[0] if gfuncs else None + if gfunc is None: + _l.error(f"Func does not exist at {lowered_addr}") + + bs_func = self._gfunc_to_bsfunc(gfunc) + lifted_func = self.art_lifter.lift(bs_func) + return lifted_func + @property def binary_base_addr(self) -> int: if self._binary_base_addr is None: @@ -849,6 +861,12 @@ def isinstance(obj, cls): # Internal functions that are very dangerous # + @ui_remote_eval + def __fast_function(self, lowered_addr: int) -> List["GhidraFunction"]: + return [ + self.currentProgram.getFunctionManager().getFunctionContaining(self.flat_api.toAddr(lowered_addr)) + ] + @ui_remote_eval def __functions(self) -> List[Tuple[int, str, int]]: return [ diff --git a/libbs/decompilers/ida/compat.py b/libbs/decompilers/ida/compat.py index da3c702c..84d0df18 100644 --- a/libbs/decompilers/ida/compat.py +++ b/libbs/decompilers/ida/compat.py @@ -97,6 +97,18 @@ def _requires_decompilation(*args, **kwargs): return _requires_decompilation +@execute_write +def get_func_ret_type(ea): + tinfo = ida_typeinf.tinfo_t() + got_info = idaapi.get_tinfo(tinfo, ea) + return str(tinfo.get_rettype()) if got_info else None + + +@execute_write +def get_func(ea): + return idaapi.get_func(ea) + + def set_func_ret_type(ea, return_type_str): tinfo = ida_typeinf.tinfo_t() if not idaapi.get_tinfo(tinfo, ea): diff --git a/libbs/decompilers/ida/hooks.py b/libbs/decompilers/ida/hooks.py index 098078a4..8f14da7f 100644 --- a/libbs/decompilers/ida/hooks.py +++ b/libbs/decompilers/ida/hooks.py @@ -41,7 +41,7 @@ from . import compat from libbs.artifacts import ( FunctionHeader, StackVariable, - Comment, GlobalVariable, Enum, Struct + Comment, GlobalVariable, Enum, Struct, Context ) @@ -56,6 +56,16 @@ IDA_EXTRA_CMT = "extra" IDA_CMT_TYPES = {IDA_CMT_CMT, IDA_EXTRA_CMT, IDA_RANGE_CMT} +FORM_TYPE_TO_NAME = { + idaapi.BWN_PSEUDOCODE: "decompilation", + idaapi.BWN_DISASM: "disassembly", + idaapi.BWN_FUNCS: "functions", + idaapi.BWN_STRUCTS: "structs", + idaapi.BWN_ENUMS: "enums", +} + +FUNC_FORMS = {"decompilation", "disassembly"} + def while_should_watch(func): @functools.wraps(func) @@ -90,20 +100,25 @@ def __init__(self, interface: "IDAInterface"): super(ScreenHook, self).__init__() def view_click(self, view, event): - form_type = idaapi.get_widget_type(view) - decomp_view = idaapi.get_widget_vdui(view) - if not form_type: + if not self.interface._artifact_watchers_started: return - # check if view is decomp or disassembly before doing expensive ea lookup - if not decomp_view and not form_type == idaapi.BWN_DISASM: - return - - ea = idc.get_screen_ea() - if not ea: + form_type = idaapi.get_widget_type(view) + #decomp_view = idaapi.get_widget_vdui(view) + if not form_type: return - self.interface.update_active_context(ea) + view_name = FORM_TYPE_TO_NAME.get(form_type, "unknown") + ctx = Context(screen_name=view_name) + if view_name in FUNC_FORMS: + ctx.addr = idaapi.get_screen_ea() + func = idaapi.get_func(ctx.addr) + if func is not None: + ctx.func_addr = func.start_ea + + 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): diff --git a/libbs/decompilers/ida/interface.py b/libbs/decompilers/ida/interface.py index 0b600abb..388387e4 100755 --- a/libbs/decompilers/ida/interface.py +++ b/libbs/decompilers/ida/interface.py @@ -9,7 +9,8 @@ import libbs from libbs.api.decompiler_interface import DecompilerInterface from libbs.artifacts import ( - StackVariable, Function, FunctionHeader, Struct, Comment, GlobalVariable, Enum, Patch, Artifact, Decompilation + StackVariable, Function, FunctionHeader, Struct, Comment, GlobalVariable, Enum, Patch, Artifact, Decompilation, + Context ) from libbs.api.decompiler_interface import requires_decompilation from . import compat @@ -28,6 +29,7 @@ def __init__(self, **kwargs): self._ctx_menu_names = [] self._ui_hooks = [] self._artifact_watcher_hooks = [] + self._gui_active_context = None super().__init__( name="ida", qt_version="PyQt5", artifact_lifter=IDAArtifactLifter(self), @@ -168,11 +170,33 @@ def _decompile(self, function: Function, map_lines=False, **kwargs) -> Optional[ return decompilation + def fast_get_function(self, func_addr) -> Optional[Function]: + lowered_addr = self.art_lifter.lower_addr(func_addr) + ida_func = compat.get_func(lowered_addr) + if ida_func is None: + _l.error(f"Function does not exist at {lowered_addr}") + return None + + ret_type = compat.get_func_ret_type(lowered_addr) + name = compat.get_func_name(lowered_addr) + lowered_func = Function( + addr=lowered_addr, + size=ida_func.size(), + header=FunctionHeader( + addr=lowered_addr, + name=name, + type_=ret_type + ) + ) + + return self.art_lifter.lift(lowered_func) + # # GUI API # def start_artifact_watchers(self): + super().start_artifact_watchers() self._artifact_watcher_hooks = [ IDBHooks(self), # this hook is special because it relies on the decompiler being present, which can only be checked @@ -184,19 +208,18 @@ def start_artifact_watchers(self): hook.hook() def stop_artifact_watchers(self): + super().stop_artifact_watchers() for hook in self._artifact_watcher_hooks: hook.unhook() - def gui_active_context(self): - if not self._init_plugin: - bs_func = self._ea_to_func(compat.get_screen_ea()) - if bs_func is None: - return None - - bs_func.addr = self.art_lifter.lift_addr(bs_func.addr) - return bs_func + def gui_active_context(self) -> Context: + if self._gui_active_context is None: + low_addr = compat.get_screen_ea() + self._gui_active_context = Context( + addr=self.art_lifter.lift_addr(low_addr) if low_addr is not None else None + ) - return self._updated_ctx + return self._gui_active_context def gui_goto(self, func_addr) -> None: func_addr = self.art_lifter.lower_addr(func_addr) @@ -351,14 +374,6 @@ def _set_function_header(self, fheader: FunctionHeader, **kwargs) -> bool: # utils # - def update_active_context(self, addr): - bs_func = self._ea_to_func(addr) - if bs_func is None: - return - - bs_func.addr = self.art_lifter.lift_addr(bs_func.addr) - self._updated_ctx = bs_func - @staticmethod def _ea_to_func(addr): if not addr or addr == idaapi.BADADDR: diff --git a/tests/test_decompilers.py b/tests/test_decompilers.py index 87928f77..8c31e420 100644 --- a/tests/test_decompilers.py +++ b/tests/test_decompilers.py @@ -286,5 +286,21 @@ def test_decompile_api(self): self.deci.shutdown() + def test_fast_function_api(self): + for dec_name in [GHIDRA_DECOMPILER, BINJA_DECOMPILER, ANGR_DECOMPILER]: + deci = DecompilerInterface.discover( + force_decompiler=dec_name, + headless=True, + headless_dec_path=DEC_TO_HEADLESS[dec_name], + binary_path=TEST_BINARY_DIR / "fauxware", + ) + self.deci = deci + main_func_addr = deci.art_lifter.lift_addr(0x40071d) + main_func = deci.fast_get_function(main_func_addr) + assert main_func is not None + assert main_func.name is not None + + self.deci.shutdown() + if __name__ == "__main__": unittest.main() diff --git a/tests/test_remote_ghidra.py b/tests/test_remote_ghidra.py index 6c1f0cf4..0cc6223a 100644 --- a/tests/test_remote_ghidra.py +++ b/tests/test_remote_ghidra.py @@ -44,7 +44,7 @@ def test_ghidra_artifact_watchers(self): hits = defaultdict(list) def func_hit(*args, **kwargs): hits[args[0].__class__].append(args[0]) - deci.artifact_write_callbacks = { + deci.artifact_change_callbacks = { typ: [func_hit] for typ in (FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment) }