declib 3.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. declib/__init__.py +9 -0
  2. declib/__main__.py +190 -0
  3. declib/api/__init__.py +13 -0
  4. declib/api/artifact_dict.py +153 -0
  5. declib/api/artifact_lifter.py +161 -0
  6. declib/api/decompiler_client.py +1219 -0
  7. declib/api/decompiler_interface.py +1261 -0
  8. declib/api/decompiler_server.py +782 -0
  9. declib/api/server_registry.py +171 -0
  10. declib/api/type_definition_parser.py +201 -0
  11. declib/api/type_parser.py +409 -0
  12. declib/api/utils.py +31 -0
  13. declib/artifacts/__init__.py +93 -0
  14. declib/artifacts/artifact.py +311 -0
  15. declib/artifacts/comment.py +49 -0
  16. declib/artifacts/context.py +61 -0
  17. declib/artifacts/decompilation.py +35 -0
  18. declib/artifacts/enum.py +53 -0
  19. declib/artifacts/formatting.py +27 -0
  20. declib/artifacts/func.py +433 -0
  21. declib/artifacts/global_variable.py +31 -0
  22. declib/artifacts/patch.py +49 -0
  23. declib/artifacts/segment.py +37 -0
  24. declib/artifacts/stack_variable.py +50 -0
  25. declib/artifacts/struct.py +184 -0
  26. declib/artifacts/typedef.py +59 -0
  27. declib/cli/__init__.py +3 -0
  28. declib/cli/decompiler_cli.py +1487 -0
  29. declib/configuration.py +184 -0
  30. declib/decompiler_stubs/__init__.py +0 -0
  31. declib/decompiler_stubs/angr_declib/__init__.py +4 -0
  32. declib/decompiler_stubs/binja_declib/__init__.py +4 -0
  33. declib/decompiler_stubs/ida_declib.py +8 -0
  34. declib/decompilers/__init__.py +8 -0
  35. declib/decompilers/angr/__init__.py +11 -0
  36. declib/decompilers/angr/artifact_lifter.py +46 -0
  37. declib/decompilers/angr/compat.py +262 -0
  38. declib/decompilers/angr/interface.py +949 -0
  39. declib/decompilers/binja/__init__.py +0 -0
  40. declib/decompilers/binja/artifact_lifter.py +32 -0
  41. declib/decompilers/binja/hooks.py +201 -0
  42. declib/decompilers/binja/interface.py +795 -0
  43. declib/decompilers/ghidra/__init__.py +0 -0
  44. declib/decompilers/ghidra/artifact_lifter.py +60 -0
  45. declib/decompilers/ghidra/compat/__init__.py +0 -0
  46. declib/decompilers/ghidra/compat/headless.py +156 -0
  47. declib/decompilers/ghidra/compat/imports.py +78 -0
  48. declib/decompilers/ghidra/compat/state.py +54 -0
  49. declib/decompilers/ghidra/compat/transaction.py +30 -0
  50. declib/decompilers/ghidra/hooks.py +242 -0
  51. declib/decompilers/ghidra/interface.py +1433 -0
  52. declib/decompilers/ida/__init__.py +0 -0
  53. declib/decompilers/ida/artifact_lifter.py +51 -0
  54. declib/decompilers/ida/compat.py +2054 -0
  55. declib/decompilers/ida/hooks.py +700 -0
  56. declib/decompilers/ida/ida_ui.py +80 -0
  57. declib/decompilers/ida/interface.py +659 -0
  58. declib/logger.py +101 -0
  59. declib/plugin_installer.py +259 -0
  60. declib/skills/__init__.py +24 -0
  61. declib/skills/decompiler/SKILL.md +316 -0
  62. declib/ui/__init__.py +33 -0
  63. declib/ui/qt_objects.py +146 -0
  64. declib/ui/utils.py +115 -0
  65. declib/ui/version.py +14 -0
  66. declib-3.8.0.dist-info/METADATA +138 -0
  67. declib-3.8.0.dist-info/RECORD +71 -0
  68. declib-3.8.0.dist-info/WHEEL +5 -0
  69. declib-3.8.0.dist-info/entry_points.txt +3 -0
  70. declib-3.8.0.dist-info/licenses/LICENSE +24 -0
  71. declib-3.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1261 @@
1
+ import inspect
2
+ import logging
3
+ import re
4
+ import threading
5
+ from collections import defaultdict
6
+ from functools import wraps
7
+ from typing import Dict, Optional, Tuple, List, Callable, Type, Union
8
+ from pathlib import Path
9
+ import os
10
+
11
+ import networkx as nx
12
+
13
+ import declib
14
+ from declib.api.artifact_lifter import ArtifactLifter
15
+ from declib.api.artifact_dict import ArtifactDict
16
+ from declib.api.type_parser import CTypeParser, CType
17
+ from declib.configuration import DecLibConfig
18
+ from declib.artifacts import (
19
+ Artifact,
20
+ Function, FunctionHeader, StackVariable,
21
+ Comment, GlobalVariable, Patch, Segment,
22
+ Enum, Struct, FunctionArgument, Context, Decompilation, Typedef
23
+ )
24
+ from declib.decompilers import SUPPORTED_DECOMPILERS, ANGR_DECOMPILER, \
25
+ BINJA_DECOMPILER, IDA_DECOMPILER, GHIDRA_DECOMPILER
26
+
27
+ _l = logging.getLogger(name=__name__)
28
+
29
+
30
+ def requires_decompilation(f):
31
+ @wraps(f)
32
+ def _requires_decompilation(self, *args, **kwargs):
33
+ if self._decompiler_available:
34
+ for arg in args:
35
+ if isinstance(arg, Function) and arg.dec_obj is None:
36
+ arg.dec_obj = self.get_decompilation_object(arg)
37
+
38
+ return f(self, *args, **kwargs)
39
+ return _requires_decompilation
40
+
41
+
42
+ class DecompilerInterface:
43
+ def __init__(
44
+ self,
45
+ # these flags should mostly be unchanged when passed through subclasses
46
+ name: str = "generic",
47
+ qt_version: str = "PySide6",
48
+ default_func_prefix: str = "sub_",
49
+ artifact_lifter: Optional[ArtifactLifter] = None,
50
+ error_on_artifact_duplicates: bool = False,
51
+ decompiler_available: bool = True,
52
+ supports_undo: bool = False,
53
+ supports_type_scopes: bool = False,
54
+ # these flags can be changed by subclassed decis
55
+ headless: bool = False,
56
+ binary_path: Optional[Union[Path, str]] = None,
57
+ init_plugin: bool = False,
58
+ plugin_name: str = f"generic_declib_plugin",
59
+ config: Optional[DecLibConfig] = None,
60
+ # [category/name] = (action_string, callback_func)
61
+ gui_ctx_menu_actions: Optional[dict] = None,
62
+ gui_init_args: Optional[Tuple] = None,
63
+ gui_init_kwargs: Optional[Dict] = None,
64
+ # [artifact_class] = list(callback_func)
65
+ artifact_change_callbacks: Optional[Dict[Type[Artifact], List[Callable]]] = None,
66
+ undo_event_callbacks: Optional[List[Callable]] = None,
67
+ decompiler_opened_callbacks: Optional[List[Callable]] = None,
68
+ decompiler_closed_callbacks: Optional[List[Callable]] = None,
69
+ thread_artifact_callbacks: bool = True,
70
+ force_click_recording: bool = False,
71
+ track_mouse_moves: bool = False,
72
+ **kwargs,
73
+ ):
74
+ self.name = name
75
+ self.art_lifter = artifact_lifter
76
+ self.type_parser = CTypeParser()
77
+ self.supports_undo = supports_undo
78
+ self.supports_type_scopes = supports_type_scopes
79
+ self.qt_version = qt_version
80
+ self.default_func_prefix = default_func_prefix
81
+ self._error_on_artifact_duplicates = error_on_artifact_duplicates
82
+
83
+ self.headless = headless
84
+ self._binary_path = Path(binary_path) if binary_path else None
85
+ self._init_plugin = init_plugin
86
+ self._unparsed_gui_ctx_actions: dict[str, tuple[str, Callable]] = gui_ctx_menu_actions or {}
87
+ # (category, name, action_string, callback_func)
88
+ self._gui_ctx_menu_actions = []
89
+ self._plugin_name = plugin_name
90
+ self.gui_plugin = None
91
+ self.artifact_watchers_started = False
92
+ self.force_click_recording = force_click_recording
93
+ self.track_mouse_moves = track_mouse_moves
94
+
95
+ # locks
96
+ self.artifact_write_lock = threading.Lock()
97
+
98
+ # callback functions, keyed by Artifact class
99
+ self.artifact_change_callbacks = artifact_change_callbacks or defaultdict(list)
100
+ self.undo_event_callbacks = undo_event_callbacks or []
101
+ self.decompiler_opened_callbacks = decompiler_opened_callbacks or []
102
+ self.decompiler_closed_callbacks = decompiler_closed_callbacks or []
103
+ self._thread_artifact_callbacks = thread_artifact_callbacks
104
+
105
+ # artifact dict aliases:
106
+ # these are the public API for artifacts that are used by the decompiler interface
107
+ self.functions = ArtifactDict(Function, self, error_on_duplicate=error_on_artifact_duplicates)
108
+ self.comments = ArtifactDict(Comment, self, error_on_duplicate=error_on_artifact_duplicates)
109
+ self.patches = ArtifactDict(Patch, self, error_on_duplicate=error_on_artifact_duplicates)
110
+ self.global_vars = ArtifactDict(GlobalVariable, self, error_on_duplicate=error_on_artifact_duplicates)
111
+ self.segments = ArtifactDict(Segment, self, error_on_duplicate=error_on_artifact_duplicates)
112
+ self.structs = ArtifactDict(Struct, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
113
+ self.enums = ArtifactDict(Enum, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
114
+ self.typedefs = ArtifactDict(Typedef, self, error_on_duplicate=error_on_artifact_duplicates, scopable=True)
115
+
116
+ self._decompiler_available = decompiler_available
117
+ # override the file-saved config when one is passed in manually, otherwise
118
+ # either load it from the filesystem or create a new one and place it there
119
+ self.config = config if config is not None else DecLibConfig.update_or_make()
120
+
121
+ if not self.headless:
122
+ args = gui_init_args or []
123
+ kwargs = gui_init_kwargs or {}
124
+ self._init_gui_components(*args, **kwargs)
125
+ else:
126
+ self._init_headless_components()
127
+
128
+ self.debug(f"Using configuration file: {self.config.save_location}")
129
+ self.config.save()
130
+
131
+ def _init_headless_components(self, *args, **kwargs):
132
+ if not self._binary_path.exists():
133
+ raise FileNotFoundError("You must provide a valid target binary path when using headless mode.")
134
+
135
+ def _deinit_headless_components(self):
136
+ pass
137
+
138
+ def _init_gui_components(self, *args, **kwargs):
139
+ from declib.ui.version import set_ui_version
140
+ set_ui_version(self.qt_version)
141
+
142
+ # register a real plugin in the GUI
143
+ if self._init_plugin:
144
+ self.gui_plugin = self._init_gui_plugin(*args, **kwargs)
145
+
146
+ # parse & register all context menu actions
147
+ self.gui_register_ctx_menu_many(self._unparsed_gui_ctx_actions)
148
+
149
+ def _init_gui_plugin(self, *args, **kwargs):
150
+ return None
151
+
152
+ def shutdown(self):
153
+ if self.artifact_watchers_started:
154
+ self.stop_artifact_watchers()
155
+ if self.headless:
156
+ self._deinit_headless_components()
157
+
158
+ #
159
+ # Public API:
160
+ # These functions are the main API for interacting with the decompiler. In general, every function that takes
161
+ # an Artifact (including addresses) should be in the lifted form. Additionally, every function that returns an
162
+ # Artifact should be in the lifted form. This is to ensure that the decompiler interface is always in sync with
163
+ # the lifter. For getting and setting artifacts, the ArtifactDicts defined in the init should be used.
164
+ #
165
+
166
+ #
167
+ # GUI API
168
+ #
169
+
170
+ def gui_active_context(self) -> Optional[declib.artifacts.Context]:
171
+ """
172
+ Returns the active location that the user is currently _clicked_ on in the decompiler.
173
+ This is returned as a Context object, which can address and screen naming information dependent
174
+ on the decompilers exposed data.
175
+ """
176
+ raise NotImplementedError
177
+
178
+ def gui_goto(self, func_addr) -> None:
179
+ """
180
+ Relocates decompiler display to provided address
181
+
182
+ @param func_addr:
183
+ @return:
184
+ """
185
+ raise NotImplementedError
186
+
187
+ def gui_show_type(self, type_name: str) -> None:
188
+ """
189
+ Relocates decompiler display to type definition
190
+
191
+ Does nothing if not implemented in a subclass
192
+ """
193
+ pass
194
+
195
+ def gui_register_ctx_menu(self, name, action_string, callback_func, category=None, shortcut=None) -> bool:
196
+ """
197
+ Register a context menu / plugin action.
198
+
199
+ :param name: unique identifier for the action
200
+ :param action_string: human-readable label shown in the menu
201
+ :param callback_func: function to invoke when the action fires
202
+ :param category: optional menu category / sub-path
203
+ :param shortcut: optional keyboard shortcut in Qt format (e.g. "Ctrl+Shift+D").
204
+ Implementations translate this to their native format. When the native
205
+ decompiler cannot bind a shortcut programmatically, this is a no-op.
206
+ """
207
+ raise NotImplementedError
208
+
209
+ def gui_ask_for_string(self, question, title="Plugin Question", default="") -> str:
210
+ """
211
+ Opens a GUI dialog box that asks the user for a string. If not overriden by the decompiler interface,
212
+ this will default to a Qt dialog box that is based on the decompilers Qt version.
213
+ """
214
+ from declib.ui.utils import gui_ask_for_string
215
+ return gui_ask_for_string(question, title=title, default=default)
216
+
217
+ def gui_ask_for_choice(self, question: str, choices: list, title="Plugin Question") -> str:
218
+ """
219
+ Opens a GUI dialog box that asks the user for a choice. If not overriden by the decompiler interface,
220
+ this will default to a Qt dialog box that is based on the decompilers Qt version.
221
+ """
222
+ from declib.ui.utils import gui_ask_for_choice
223
+ return gui_ask_for_choice(question, choices, title=title)
224
+
225
+ def gui_popup_text(self, text: str, title: str = "Plugin Message") -> bool:
226
+ """
227
+ Opens a GUI dialog box that displays a message. If not overriden by the decompiler interface,
228
+ this will default to a Qt dialog box that is based on the decompilers Qt version.
229
+ """
230
+ from declib.ui.utils import gui_popup_text
231
+ return gui_popup_text(text, title=title)
232
+
233
+ def gui_run_on_main_thread(self, func: Callable, *args, **kwargs):
234
+ """
235
+ Runs the provided function on the main thread of the GUI. This is useful for updating the GUI from a
236
+ background thread. Only in Ghidra is this useful.
237
+ """
238
+ return func(*args, **kwargs)
239
+
240
+ def gui_attach_qt_window(self, qt_window: type["QWidgt"], title: str, target_window=None, position=None, *args, **kwargs) -> bool:
241
+ """
242
+ Attaches a Qt window to the decompiler interface. This is useful for embedding custom Qt windows
243
+ into the decompiler interface.
244
+ """
245
+ raise NotImplementedError
246
+
247
+ @staticmethod
248
+ def _parse_ctx_menu_actions(actions: dict[str, tuple[str, Callable]]) -> List[Tuple[str, str, str, Callable]]:
249
+ gui_ctx_menu_actions = []
250
+ for combined_name, items in actions.items():
251
+ slashes = list(re.finditer("/", combined_name))
252
+ if not slashes:
253
+ category = ""
254
+ name = combined_name
255
+ else:
256
+ last_slash = slashes[-1]
257
+ category = combined_name[:last_slash.start()]
258
+ name = combined_name[last_slash.start()+1:]
259
+
260
+ gui_ctx_menu_actions.append((category, name,) + items)
261
+
262
+ return gui_ctx_menu_actions
263
+
264
+ def gui_register_ctx_menu_many(self, actions: dict[str, tuple[str, Callable]]):
265
+ parsed_actions = self._parse_ctx_menu_actions(actions)
266
+ for action in parsed_actions:
267
+ category, name, action_string, callback_func = action[:4]
268
+ shortcut = action[4] if len(action) > 4 else None
269
+ self.gui_register_ctx_menu(
270
+ name, action_string, callback_func, category=category, shortcut=shortcut
271
+ )
272
+
273
+ #
274
+ # Override Mandatory API
275
+ #
276
+
277
+ def start_artifact_watchers(self):
278
+ """
279
+ Starts the artifact watchers for the decompiler. This is a special function that is called
280
+ by the decompiler interface when the decompiler is ready to start watching for changes in the
281
+ decompiler. This is useful for plugins that want to watch for changes in the decompiler and
282
+ react to them.
283
+
284
+ @return:
285
+ """
286
+ self.debug("Starting BinSync artifact watchers...")
287
+ self.artifact_watchers_started = True
288
+
289
+ def stop_artifact_watchers(self):
290
+ """
291
+ Stops the artifact watchers for the decompiler. This is a special function that is called
292
+ by the decompiler interface when the decompiler is ready to stop watching for changes in the
293
+ decompiler. This is useful for plugins that want to watch for changes in the decompiler and
294
+ react to them.
295
+ """
296
+ self.debug("Stopping BinSync artifact watchers...")
297
+ self.artifact_watchers_started = False
298
+
299
+ @property
300
+ def binary_base_addr(self) -> int:
301
+ """
302
+ Returns the base address of the binary in the decompiler. This is useful for calculating offsets
303
+ in the binary. Also mandatory for using the lifting and lowering API.
304
+ """
305
+ raise NotImplementedError
306
+
307
+ @property
308
+ def binary_hash(self) -> str:
309
+ """
310
+ Returns a hex string of the currently loaded binary in the decompiler. For most cases,
311
+ this will simply be a md5hash of the binary.
312
+
313
+ @rtype: hex string
314
+ """
315
+ raise NotImplementedError
316
+
317
+ @property
318
+ def binary_path(self) -> Optional[str]:
319
+ """
320
+ Returns a string that is the path of the currently loaded binary. If there is no binary loaded
321
+ then None should be returned.
322
+
323
+ @rtype: path-like string (/path/to/binary)
324
+ """
325
+ return self._binary_path
326
+
327
+ def fast_get_function(self, func_addr) -> Optional[Function]:
328
+ """
329
+ Attempts to get a light version of the Function at func_addr.
330
+ This function implements special logic to be faster than grabbing all light-functions, or grabbing
331
+ a decompiled function. Use this API in the case where you may need to get a single functions info
332
+ many times in a loop.
333
+
334
+ @param func_addr:
335
+ @return:
336
+ """
337
+ raise NotImplementedError
338
+
339
+ def get_func_size(self, func_addr) -> int:
340
+ """
341
+ Returns the size of a function
342
+
343
+ @param func_addr:
344
+ @return:
345
+ """
346
+ raise NotImplementedError
347
+
348
+ @property
349
+ def decompiler_available(self) -> bool:
350
+ """
351
+ @return: True if decompiler is available for decompilation, False if otherwise
352
+ """
353
+ return True
354
+
355
+ def decompile(self, addr: int, map_lines=False, **kwargs) -> Optional[Decompilation]:
356
+ lowered_addr = self.art_lifter.lower_addr(addr)
357
+ if not self.decompiler_available:
358
+ _l.error("Decompiler is not available.")
359
+ return None
360
+
361
+ sorted_funcs = sorted(self._functions().items(), key=lambda x: x[0])
362
+ func_by_addr = {_addr: func for _addr, func in sorted_funcs}
363
+ func = None
364
+ if lowered_addr in func_by_addr:
365
+ func = func_by_addr[lowered_addr]
366
+ else:
367
+ _l.debug("Address is not a function start, searching for function...")
368
+ for func_addr, _func in sorted_funcs:
369
+ if _func.addr <= lowered_addr < (_func.addr + _func.size):
370
+ func = _func
371
+ break
372
+
373
+ if func is None:
374
+ self.warning(f"Failed to find function for address {hex(lowered_addr)}")
375
+ return None
376
+
377
+ try:
378
+ decompilation = self._decompile(func, map_lines=map_lines, **kwargs)
379
+ except Exception as e:
380
+ self.warning(f"Failed to decompile function at {hex(lowered_addr)}: {e}")
381
+ decompilation = None
382
+
383
+ if decompilation is not None:
384
+ decompilation = self.art_lifter.lift(decompilation)
385
+
386
+ return decompilation
387
+
388
+ def xrefs_to(self, artifact: Artifact, decompile=False, only_code=False) -> List[Artifact]:
389
+ """
390
+ Returns a list of artifacts that reference the provided artifact.
391
+ @param artifact: Artifact to find references to
392
+ @param decompile: If True, decompile the function before searching for xrefs
393
+ @return: List of artifacts that reference the provided artifact
394
+ """
395
+ if not isinstance(artifact, Function):
396
+ raise ValueError("Only functions are supported for xrefs_to")
397
+
398
+ return []
399
+
400
+ def xrefs_to_addr(self, addr: int, only_code: bool = False) -> List[Artifact]:
401
+ """Return artifacts that reference ``addr``.
402
+
403
+ Unlike :meth:`xrefs_to`, which assumes a Function target and therefore
404
+ only fires on function entry points, this is a raw "who references
405
+ this address?" query. It's what you want after ``list_strings`` finds
406
+ a candidate string and you need to know which functions read it.
407
+
408
+ The default implementation turns the address into a stub Function and
409
+ delegates to :meth:`xrefs_to`; subclasses should override this with a
410
+ real data-xref query when their backend exposes one.
411
+
412
+ @param addr: Address (lifted) to find references to.
413
+ @param only_code: Restrict to code references if the backend supports it.
414
+ @return: List of referencing artifacts (typically Function stubs).
415
+ """
416
+ return self.xrefs_to(Function(addr, 0), only_code=only_code)
417
+
418
+ def xrefs_from(self, func_addr: int) -> List[Function]:
419
+ """Return the functions that ``func_addr`` calls (its direct callees).
420
+
421
+ The default implementation falls back to get_callgraph + out_edges,
422
+ which is expensive because it computes xrefs for every function in
423
+ the binary. Subclasses should override with a direct per-function
424
+ callee query when their backend exposes one.
425
+ """
426
+ try:
427
+ cg = self.get_callgraph(only_names=False)
428
+ except Exception as exc:
429
+ _l.debug("get_callgraph failed: %s", exc)
430
+ return []
431
+ callees: List[Function] = []
432
+ seen = set()
433
+ for caller, callee in cg.out_edges(nbunch=None):
434
+ if getattr(caller, "addr", None) != func_addr:
435
+ continue
436
+ callee_addr = getattr(callee, "addr", None)
437
+ if callee_addr in seen:
438
+ continue
439
+ seen.add(callee_addr)
440
+ callees.append(callee)
441
+ return callees
442
+
443
+ def get_callers(self, target) -> List[Function]:
444
+ """
445
+ Returns a list of Functions that call/reference the provided target.
446
+
447
+ @param target: A Function, address (int), or symbol name (str).
448
+ @return: List of Function objects whose bodies reference `target`. Each result is a (light)
449
+ Function; only its addr (and name when resolvable) are guaranteed to be populated.
450
+ """
451
+ func: Optional[Function] = None
452
+ if isinstance(target, Function):
453
+ func = target
454
+ elif isinstance(target, int):
455
+ func = self.fast_get_function(target)
456
+ if func is None:
457
+ func = Function(target, 0)
458
+ elif isinstance(target, str):
459
+ for addr, light_func in self.functions.items():
460
+ if light_func.name == target:
461
+ func = self.fast_get_function(addr) or Function(addr, 0)
462
+ break
463
+ if func is None:
464
+ raise ValueError(f"Unable to locate function named {target!r}")
465
+ else:
466
+ raise ValueError(f"Unsupported target type for get_callers: {type(target)}")
467
+
468
+ callers: List[Function] = []
469
+ seen = set()
470
+ for xref in self.xrefs_to(func):
471
+ if not isinstance(xref, Function):
472
+ continue
473
+ if xref.addr in seen:
474
+ continue
475
+ seen.add(xref.addr)
476
+ if not xref.name:
477
+ resolved = self.fast_get_function(xref.addr)
478
+ if resolved is not None:
479
+ xref = resolved
480
+ callers.append(xref)
481
+
482
+ return callers
483
+
484
+ def list_strings(self, filter: Optional[str] = None) -> List[Tuple[int, str]]:
485
+ """
486
+ Returns a list of (addr, string) tuples for strings found in the binary.
487
+
488
+ This reports **only what the decompiler's own string detector
489
+ surfaced** — it is deliberately not a substitute for a full-file
490
+ scan. Backend fidelity varies (angr in particular misses most of
491
+ ``.rodata``); callers that need an exhaustive list should fall
492
+ back to external tools such as ``strings(1)``, ``rabin2 -z``, or
493
+ ``readelf -p`` and then use the resulting addresses with the
494
+ other APIs (``decompile``, ``xrefs_to_addr``, etc.).
495
+
496
+ Subclasses are expected to override this with native, fast string
497
+ discovery. The base implementation returns an empty list.
498
+
499
+ @param filter: Optional regex string; only strings that match will be returned.
500
+ @return: List of (address, string) tuples.
501
+ """
502
+ return []
503
+
504
+ def disassemble(self, addr: int, **kwargs) -> Optional[str]:
505
+ """
506
+ Returns the disassembly of a function as a single string.
507
+
508
+ Subclasses should override this to emit decompiler-native disassembly. The default
509
+ implementation returns None.
510
+
511
+ @param addr: Address of the function (or any address inside the function).
512
+ @return: The disassembly string, or None if unavailable.
513
+ """
514
+ return None
515
+
516
+ def read_memory(self, addr: int, size: int) -> Optional[bytes]:
517
+ """Read ``size`` bytes from the loaded program at ``addr``.
518
+
519
+ Returns the raw bytes the backend has for the requested span. ``None``
520
+ means "I couldn't satisfy the read at all" — out-of-range, uninitialized,
521
+ or the backend can't reach that memory. A short read (fewer bytes than
522
+ requested) is still valid and returned as-is; callers should check
523
+ ``len(result)`` if they need an exact count.
524
+
525
+ @param addr: Lifted address to start reading from.
526
+ @param size: Number of bytes to read. Must be > 0.
527
+ @return: Bytes read, or ``None`` if the backend can't read this region.
528
+ """
529
+ raise NotImplementedError
530
+
531
+ def get_callgraph(self, only_names=False) -> nx.DiGraph:
532
+ """
533
+ Returns the callgraph of the binary. This is a dict of function addresses to a list of function addresses
534
+ that the function calls.
535
+ """
536
+ callgraph = nx.DiGraph()
537
+ for func in self.functions.values():
538
+ callers = self.xrefs_to(func)
539
+ for caller in callers:
540
+ if isinstance(caller, Function):
541
+ if only_names:
542
+ callgraph.add_edge(caller.name, func.name)
543
+ else:
544
+ callgraph.add_edge(caller, func)
545
+
546
+ return callgraph
547
+
548
+ def get_dependencies(self, artifact: Artifact, decompile=True, max_resolves=50, **kwargs) -> List[Artifact]:
549
+ if not isinstance(artifact, Function):
550
+ raise ValueError("Only functions are supported for get_dependencies")
551
+
552
+ # collect all xrefs to the function (for global variables)
553
+ if decompile:
554
+ # the function was never decompiled
555
+ if artifact.dec_obj is None:
556
+ # TODO: this needs to be fixed so that it still works without redecompiling. What if we want
557
+ # to do analysis on a function that is not set yet.
558
+ artifact = self.functions[artifact.addr]
559
+
560
+ art_users = self.xrefs_to(artifact, decompile=decompile)
561
+ gvars = [art for art in art_users if isinstance(art, GlobalVariable)]
562
+
563
+ # collect all structs/enums used in the function types
564
+ imported_types = set()
565
+ imported_types.add(self.get_defined_type(artifact.header.type))
566
+ for arg in artifact.header.args.values():
567
+ imported_types.add(self.get_defined_type(arg.type))
568
+ for svar in artifact.stack_vars.values():
569
+ imported_types.add(self.get_defined_type(svar.type))
570
+
571
+ # start resolving dependencies in structs
572
+ for _ in range(max_resolves):
573
+ new_imports = False
574
+ for imported_type in list(imported_types):
575
+ if isinstance(imported_type, Struct):
576
+ for member in imported_type.members.values():
577
+ new_type = self.get_defined_type(member.type)
578
+ if new_type is not None and new_type not in imported_types:
579
+ imported_types.add(new_type)
580
+ new_imports = True
581
+ break
582
+
583
+ if new_imports:
584
+ break
585
+
586
+ if isinstance(imported_type, Typedef):
587
+ new_type = self.get_defined_type(imported_type.type)
588
+ if new_type is not None and new_type not in imported_types:
589
+ imported_types.add(new_type)
590
+ new_imports = True
591
+
592
+ if not new_imports:
593
+ break
594
+ else:
595
+ self.warning("Max dependency resolves reached, returning partial results")
596
+
597
+ all_deps = [art for art in list(imported_types) + gvars if art is not None]
598
+ return all_deps
599
+
600
+ def get_func_containing(self, addr: int) -> Optional[Function]:
601
+ raise NotImplementedError
602
+
603
+ def _decompile(self, function: Function, map_lines=False, **kwargs) -> Optional[Decompilation]:
604
+ raise NotImplementedError
605
+
606
+ def get_decompilation_object(self, function: Function, **kwargs) -> Optional[object]:
607
+ raise NotImplementedError
608
+
609
+ def should_watch_artifacts(self) -> bool:
610
+ return True
611
+
612
+ #
613
+ # Override Optional API:
614
+ # These are API that provide extra introspection for plugins that may rely on DecLib Interface
615
+ #
616
+
617
+ @property
618
+ def binary_arch(self) -> str:
619
+ """
620
+ Returns a string of the currently loaded binary's architecture.
621
+ """
622
+ raise NotImplementedError
623
+
624
+ @property
625
+ def default_pointer_size(self) -> int:
626
+ """
627
+ Returns the default pointer size of the binary. This is useful for calculating offsets
628
+ in the binary.
629
+ """
630
+ raise NotImplementedError
631
+
632
+ def undo(self):
633
+ """
634
+ Undoes the last change made to the decompiler.
635
+ """
636
+ raise NotImplementedError
637
+
638
+ def local_variable_names(self, func: Function) -> List[str]:
639
+ """
640
+ Returns a list of local variable names for a function. Note, these also include register variables
641
+ that are normally not liftable in DecLib.
642
+ @param func: Function to get local variable names for
643
+ @return: List of local variable names
644
+ """
645
+ return []
646
+
647
+ def rename_local_variables_by_names(self, func: Function, name_map: Dict[str, str], **kwargs) -> bool:
648
+ """
649
+ Renames local variables in a function by a name map. Note, these also include register variables
650
+ that are normally not liftable in DecLib.
651
+ @param func: Function to rename local variables in
652
+ @param name_map: Dictionary of old name to new name
653
+ @return: True if any local variables were renamed, False if otherwise
654
+ """
655
+ return False
656
+
657
+ #
658
+ # Private Artifact API:
659
+ # Unlike the public API, every function in this section should take and return artifacts in their native (lowered)
660
+ # form.
661
+ #
662
+
663
+ # functions
664
+ def _set_function(self, func: Function, **kwargs) -> bool:
665
+ update = False
666
+ header = func.header
667
+ if header is not None:
668
+ update |= self._set_function_header(header, **kwargs)
669
+
670
+ if func.stack_vars:
671
+ update |= self._set_stack_variables(list(func.stack_vars.values()), **kwargs)
672
+
673
+ return update
674
+
675
+ def _get_function(self, addr, **kwargs) -> Optional[Function]:
676
+ return None
677
+
678
+ def _del_function(self, addr, **kwargs) -> bool:
679
+ return False
680
+
681
+ def _functions(self) -> Dict[int, Function]:
682
+ """
683
+ Returns a dict of declib.Functions that contain the addr, name, and size of each function in the decompiler.
684
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
685
+ exists. To get live artifacts, use the singleton function of the same name.
686
+
687
+ @return:
688
+ """
689
+ return {}
690
+
691
+ # stack vars
692
+ def _set_stack_variables(self, svars: List[StackVariable], **kwargs) -> bool:
693
+ update = False
694
+ for svar in svars:
695
+ update |= self._set_stack_variable(svar, **kwargs)
696
+
697
+ return update
698
+
699
+ def _set_stack_variable(self, svar: StackVariable, **kwargs) -> bool:
700
+ return False
701
+
702
+ def _get_stack_variable(self, addr: int, offset: int, **kwargs) -> Optional[StackVariable]:
703
+ func = self._get_function(addr, **kwargs)
704
+ if func is None:
705
+ return None
706
+
707
+ return func.stack_vars.get(offset, None)
708
+
709
+ def _del_stack_variable(self, addr: int, offset: int, **kwargs) -> bool:
710
+ return False
711
+
712
+ def _stack_variables(self, **kwargs) -> Dict[int,Dict[int, StackVariable]]:
713
+ stack_vars = defaultdict(dict)
714
+ for addr in self._functions():
715
+ func = self._get_function(addr, **kwargs)
716
+ for svar in func.stack_vars.values():
717
+ stack_vars[addr][svar.offset] = svar
718
+
719
+ return dict(stack_vars)
720
+
721
+ # global variables
722
+ def _set_global_variable(self, gvar: GlobalVariable, **kwargs) -> bool:
723
+ return False
724
+
725
+ def _get_global_var(self, addr) -> Optional[GlobalVariable]:
726
+ return None
727
+
728
+ def _del_global_var(self, addr) -> bool:
729
+ return False
730
+
731
+ def _global_vars(self, **kwargs) -> Dict[int, GlobalVariable]:
732
+ """
733
+ Returns a dict of declib.GlobalVariable that contain the addr and size of each global var.
734
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
735
+ exists. To get live artifacts, use the singleton function of the same name.
736
+
737
+ @return:
738
+ """
739
+ return {}
740
+
741
+ # structs
742
+ def _set_struct(self, struct: Struct, header=True, members=True, **kwargs) -> bool:
743
+ return False
744
+
745
+ def _get_struct(self, name) -> Optional[Struct]:
746
+ return None
747
+
748
+ def _del_struct(self, name) -> bool:
749
+ return False
750
+
751
+ def _structs(self) -> Dict[str, Struct]:
752
+ """
753
+ Returns a dict of declib.Structs that contain the name and size of each struct in the decompiler.
754
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
755
+ exists. To get live artifacts, use the singleton function of the same name.
756
+
757
+ @return:
758
+ """
759
+ return {}
760
+
761
+ # enums
762
+ def _set_enum(self, enum: Enum, **kwargs) -> bool:
763
+ return False
764
+
765
+ def _get_enum(self, name) -> Optional[Enum]:
766
+ return None
767
+
768
+ def _del_enum(self, name) -> bool:
769
+ return False
770
+
771
+ def _enums(self) -> Dict[str, Enum]:
772
+ """
773
+ Returns a dict of declib.Enum that contain the name of the enums in the decompiler.
774
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
775
+ exists. To get live artifacts, use the singleton function of the same name.
776
+
777
+ @return:
778
+ """
779
+ return {}
780
+
781
+ # typedefs
782
+ def _set_typedef(self, typedef: Typedef, **kwargs) -> bool:
783
+ return False
784
+
785
+ def _get_typedef(self, name) -> Optional[Typedef]:
786
+ return None
787
+
788
+ def _del_typedef(self, name) -> bool:
789
+ return False
790
+
791
+ def _typedefs(self) -> Dict[str, Typedef]:
792
+ """
793
+ Returns a dict of declib.Typedef that contain the name of the typedefs in the decompiler.
794
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
795
+ exists. To get live artifacts, use the singleton function of the same name.
796
+
797
+ @return:
798
+ """
799
+ return {}
800
+
801
+ # patches
802
+ def _set_patch(self, patch: Patch, **kwargs) -> bool:
803
+ return False
804
+
805
+ def _get_patch(self, addr) -> Optional[Patch]:
806
+ return None
807
+
808
+ def _del_patch(self, addr) -> bool:
809
+ return False
810
+
811
+ def _patches(self) -> Dict[int, Patch]:
812
+ """
813
+ Returns a dict of declib.Patch that contain the addr of each Patch and the bytes.
814
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
815
+ exists. To get live artifacts, use the singleton function of the same name.
816
+
817
+ @return:
818
+ """
819
+ return {}
820
+
821
+ # comments
822
+ def _set_comment(self, comment: Comment, **kwargs) -> bool:
823
+ return False
824
+
825
+ def _get_comment(self, addr) -> Optional[Comment]:
826
+ return None
827
+
828
+ def _del_comment(self, addr) -> bool:
829
+ return False
830
+
831
+ def _comments(self) -> Dict[int, Comment]:
832
+ return {}
833
+
834
+ # segments
835
+ def _set_segment(self, segment: Segment, **kwargs) -> bool:
836
+ return False
837
+
838
+ def _get_segment(self, name) -> Optional[Segment]:
839
+ return None
840
+
841
+ def _del_segment(self, name) -> bool:
842
+ return False
843
+
844
+ def _segments(self) -> Dict[str, Segment]:
845
+ """
846
+ Returns a dict of declib.Segment that contain the name, start_addr, end_addr, and permissions of each segment.
847
+ Note: this does not contain the live artifacts of the Artifact, only the minimum knowledge to that the Artifact
848
+ exists. To get live artifacts, use the singleton function of the same name.
849
+
850
+ @return:
851
+ """
852
+ return {}
853
+
854
+ # others...
855
+ def _set_function_header(self, fheader: FunctionHeader, **kwargs) -> bool:
856
+ return False
857
+
858
+ #
859
+ # Change Callback API:
860
+ # Every callback in this group assumes the input will be decompiler-specific (lowered) and will
861
+ # lift it ONCE inside this function. Each one will return the lifted form, for easier overriding.
862
+ #
863
+
864
+ def decompiler_opened_event(self, **kwargs):
865
+ """
866
+ This function is called when the decompiler platform this interface is running on is opened for the first time.
867
+ In the presence of a decompiler with multiple tabs, this function will still only be called once.
868
+ """
869
+ for callback_func in self.decompiler_opened_callbacks:
870
+ if self._thread_artifact_callbacks:
871
+ threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
872
+ else:
873
+ callback_func(**kwargs)
874
+
875
+ def decompiler_closed_event(self, **kwargs):
876
+ """
877
+ This function is called when the decompiler platform this interface is running on is closing/closed.
878
+ In the presence of a decompiler with multiple tabs, this function will still only be called once.
879
+ """
880
+ for callback_func in self.decompiler_closed_callbacks:
881
+ if self._thread_artifact_callbacks:
882
+ threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
883
+ else:
884
+ callback_func(**kwargs)
885
+
886
+ def gui_undo_event(self, **kwargs):
887
+ for callback_func in self.undo_event_callbacks:
888
+ if self._thread_artifact_callbacks:
889
+ threading.Thread(target=callback_func, kwargs=kwargs, daemon=True).start()
890
+ else:
891
+ callback_func(**kwargs)
892
+
893
+ def gui_context_changed(self, ctx: Context, **kwargs) -> declib.artifacts.Context:
894
+ # XXX: should this be lifted?
895
+ for callback_func in self.artifact_change_callbacks[Context]:
896
+ args = (ctx,)
897
+ if self._thread_artifact_callbacks:
898
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
899
+ else:
900
+ callback_func(*args, **kwargs)
901
+
902
+ return ctx
903
+
904
+ def segment_changed(self, segment: Segment, **kwargs) -> Segment:
905
+ lifted_segment = self.art_lifter.lift(segment)
906
+ for callback_func in self.artifact_change_callbacks[Segment]:
907
+ args = (lifted_segment,)
908
+ if self._thread_artifact_callbacks:
909
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
910
+ else:
911
+ callback_func(*args, **kwargs)
912
+
913
+ return lifted_segment
914
+
915
+ def function_header_changed(self, fheader: FunctionHeader, **kwargs) -> FunctionHeader:
916
+ lifted_fheader = self.art_lifter.lift(fheader)
917
+ for callback_func in self.artifact_change_callbacks[FunctionHeader]:
918
+ args = (lifted_fheader,)
919
+ if self._thread_artifact_callbacks:
920
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
921
+ else:
922
+ callback_func(*args, **kwargs)
923
+
924
+ return lifted_fheader
925
+
926
+ def stack_variable_changed(self, svar: StackVariable, **kwargs) -> StackVariable:
927
+ lifted_svar = self.art_lifter.lift(svar)
928
+ for callback_func in self.artifact_change_callbacks[StackVariable]:
929
+ args = (lifted_svar,)
930
+ if self._thread_artifact_callbacks:
931
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
932
+ else:
933
+ callback_func(*args, **kwargs)
934
+
935
+ return lifted_svar
936
+
937
+ def comment_changed(self, comment: Comment, deleted=False, **kwargs) -> Comment:
938
+ kwargs["deleted"] = deleted
939
+ lifted_cmt = self.art_lifter.lift(comment)
940
+ for callback_func in self.artifact_change_callbacks[Comment]:
941
+ args = (lifted_cmt,)
942
+ if self._thread_artifact_callbacks:
943
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
944
+ else:
945
+ callback_func(*args, **kwargs)
946
+
947
+ return lifted_cmt
948
+
949
+ def struct_changed(self, struct: Struct, deleted=False, **kwargs) -> Struct:
950
+ kwargs["deleted"] = deleted
951
+ lifted_struct = self.art_lifter.lift(struct)
952
+ for callback_func in self.artifact_change_callbacks[Struct]:
953
+ args = (lifted_struct,)
954
+ if self._thread_artifact_callbacks:
955
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
956
+ else:
957
+ callback_func(*args, **kwargs)
958
+
959
+ return lifted_struct
960
+
961
+ def decompilation_changed(self, decompilation: Decompilation, **kwargs) -> Decompilation:
962
+ lifted_dcmp = self.art_lifter.lift(decompilation)
963
+ for callback_func in self.artifact_change_callbacks[Decompilation]:
964
+ args = (lifted_dcmp,)
965
+ if self._thread_artifact_callbacks:
966
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
967
+ else:
968
+ callback_func(*args, **kwargs)
969
+
970
+ return lifted_dcmp
971
+
972
+ def enum_changed(self, enum: Enum, deleted=False, **kwargs) -> Enum:
973
+ kwargs["deleted"] = deleted
974
+ lifted_enum = self.art_lifter.lift(enum)
975
+ for callback_func in self.artifact_change_callbacks[Enum]:
976
+ args = (lifted_enum,)
977
+ if self._thread_artifact_callbacks:
978
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
979
+ else:
980
+ callback_func(*args, **kwargs)
981
+
982
+ return lifted_enum
983
+
984
+ def typedef_changed(self, typedef: Typedef, deleted=False, **kwargs) -> Typedef:
985
+ kwargs["deleted"] = deleted
986
+ lifted_typedef = self.art_lifter.lift(typedef)
987
+ for callback_func in self.artifact_change_callbacks[Typedef]:
988
+ args = (lifted_typedef,)
989
+ if self._thread_artifact_callbacks:
990
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
991
+ else:
992
+ callback_func(*args, **kwargs)
993
+
994
+ return lifted_typedef
995
+
996
+ def global_variable_changed(self, gvar: GlobalVariable, **kwargs) -> GlobalVariable:
997
+ lifted_gvar = self.art_lifter.lift(gvar)
998
+ for callback_func in self.artifact_change_callbacks[GlobalVariable]:
999
+ args = (lifted_gvar,)
1000
+ if self._thread_artifact_callbacks:
1001
+ threading.Thread(target=callback_func, args=args, kwargs=kwargs, daemon=True).start()
1002
+ else:
1003
+ callback_func(*args, **kwargs)
1004
+
1005
+ return lifted_gvar
1006
+
1007
+ #
1008
+ # Special Loggers and Printers
1009
+ #
1010
+
1011
+ def print(self, msg: str, **kwargs):
1012
+ print(msg)
1013
+
1014
+ def info(self, msg: str, **kwargs):
1015
+ _l.info(msg)
1016
+
1017
+ def debug(self, msg: str, **kwargs):
1018
+ _l.debug(msg)
1019
+
1020
+ def warning(self, msg: str, **kwargs):
1021
+ _l.warning(msg)
1022
+
1023
+ def error(self, msg: str, **kwargs):
1024
+ _l.error(msg)
1025
+
1026
+ #
1027
+ # Utils
1028
+ #
1029
+
1030
+ def set_artifact(self, artifact: Artifact, lower=True, **kwargs) -> bool:
1031
+ """
1032
+ Sets a declib Artifact into the decompilers local database. This operations allows you to change
1033
+ what the native decompiler sees with declib Artifacts. This is different from opertions on a declib State,
1034
+ since this is native to the decompiler
1035
+
1036
+ >>> func = Function(0xdeadbeef, 0x800)
1037
+ >>> func.name = "main"
1038
+ >>> deci.set_artifact(func)
1039
+
1040
+ @param artifact:
1041
+ @param lower: Wether to convert the Artifacts types and offset into the local decompilers format
1042
+ @return: True if the Artifact was succesfuly set into the decompiler
1043
+ """
1044
+ set_map = {
1045
+ Function: self._set_function,
1046
+ FunctionHeader: self._set_function_header,
1047
+ StackVariable: self._set_stack_variable,
1048
+ Comment: self._set_comment,
1049
+ GlobalVariable: self._set_global_variable,
1050
+ Struct: self._set_struct,
1051
+ Enum: self._set_enum,
1052
+ Patch: self._set_patch,
1053
+ Segment: self._set_segment,
1054
+ Artifact: None,
1055
+ }
1056
+
1057
+ if lower:
1058
+ artifact = self.art_lifter.lower(artifact)
1059
+
1060
+ setter = set_map.get(type(artifact), None)
1061
+ if setter is None:
1062
+ _l.critical("Unsupported object is attempting to be set, please check your object: %s", artifact)
1063
+ return False
1064
+
1065
+ return setter(artifact, **kwargs)
1066
+
1067
+ @staticmethod
1068
+ def get_identifiers(artifact: Artifact) -> Tuple:
1069
+ if isinstance(artifact, (Function, FunctionHeader, GlobalVariable, Patch, Comment)):
1070
+ return (artifact.addr,)
1071
+ elif isinstance(artifact, StackVariable):
1072
+ return artifact.addr, artifact.offset
1073
+ elif isinstance(artifact, FunctionArgument):
1074
+ # TODO: add addr to function arguments
1075
+ return (artifact.offset,)
1076
+ elif isinstance(artifact, (Struct, Enum, Typedef, Segment)):
1077
+ return (artifact.name,)
1078
+ else:
1079
+ raise ValueError(f"Unsupported artifact type: {type(artifact)}")
1080
+
1081
+ def get_defined_type(self, type_str) -> Optional[Artifact]:
1082
+ if not type_str:
1083
+ return None
1084
+
1085
+ normalized_type, scope = self.art_lifter.parse_scoped_type(type_str)
1086
+ type_: CType = self.type_parser.parse_type(normalized_type)
1087
+ if not type_:
1088
+ # it was not parseable
1089
+ return None
1090
+
1091
+ # type is a primitive that returns no base type
1092
+ base_type = type_.base_type
1093
+ if base_type is None:
1094
+ return None
1095
+
1096
+ # if we trigger here, it means it's not a user-defined type
1097
+ if not base_type.is_unknown:
1098
+ return None
1099
+
1100
+ base_type_str = base_type.type
1101
+ lifted_scoped_type = self.art_lifter.scoped_type_to_str(base_type_str, scope)
1102
+ if lifted_scoped_type in self.structs:
1103
+ return self.structs[lifted_scoped_type]
1104
+ elif lifted_scoped_type in self.enums:
1105
+ return self.enums[lifted_scoped_type]
1106
+ elif lifted_scoped_type in self.typedefs:
1107
+ return self.typedefs[lifted_scoped_type]
1108
+ else:
1109
+ return None
1110
+
1111
+ @staticmethod
1112
+ def _find_global_in_call_frames(global_name, max_frames=10):
1113
+ curr_frame = inspect.currentframe()
1114
+ outer_frames = inspect.getouterframes(curr_frame, max_frames)
1115
+ for frame in outer_frames:
1116
+ global_data = frame.frame.f_globals.get(global_name, None)
1117
+ if global_data is not None:
1118
+ return global_data
1119
+ else:
1120
+ return None
1121
+
1122
+ @staticmethod
1123
+ def find_current_decompiler(force: str = None) -> Optional[str]:
1124
+ """
1125
+ Finds the name of the current decompiler that this function is running inside of. Note, this function
1126
+ does not create an interface, but instead finds the name of the decompiler that is currently running.
1127
+ """
1128
+ available = set()
1129
+
1130
+ # Binary Ninja
1131
+ # this check needs to be done last since there is no way to traverse the stack frame to find the correct
1132
+ # BV at this point in time.
1133
+ try:
1134
+ import binaryninja
1135
+ has_bn_ui = False
1136
+ try:
1137
+ import binaryninjaui
1138
+ has_bn_ui = True
1139
+ except Exception:
1140
+ pass
1141
+
1142
+ if has_bn_ui:
1143
+ return BINJA_DECOMPILER
1144
+ available.add(BINJA_DECOMPILER)
1145
+ # error can be thrown for an invalid license
1146
+ except Exception as e:
1147
+ if "License is not valid" in str(e):
1148
+ _l.warning("Binary Ninja license is invalid, skipping...")
1149
+
1150
+ # Ghidra
1151
+ this_obj = DecompilerInterface._find_global_in_call_frames("__this__")
1152
+ if (this_obj is not None) and (hasattr(this_obj, "currentProgram")):
1153
+ available.add(GHIDRA_DECOMPILER)
1154
+ if not force:
1155
+ return GHIDRA_DECOMPILER
1156
+
1157
+ # angr-management
1158
+ try:
1159
+ import angr
1160
+ available.add(ANGR_DECOMPILER)
1161
+ import angrmanagement
1162
+ if DecompilerInterface._find_global_in_call_frames('workspace') is not None:
1163
+ return ANGR_DECOMPILER
1164
+ except Exception:
1165
+ pass
1166
+
1167
+ # IDA Pro
1168
+ try:
1169
+ import idaapi
1170
+ available.add(IDA_DECOMPILER)
1171
+ if not force:
1172
+ return IDA_DECOMPILER
1173
+ except Exception:
1174
+ pass
1175
+
1176
+ try:
1177
+ # for IDA 9 Beta
1178
+ import ida
1179
+ available.add(IDA_DECOMPILER)
1180
+ except ImportError:
1181
+ pass
1182
+ try:
1183
+ # for IDA 9+
1184
+ import idapro
1185
+ available.add(IDA_DECOMPILER)
1186
+ except Exception:
1187
+ pass
1188
+
1189
+ if not available:
1190
+ _l.critical("DecLib was unable to find the current decompiler you are running in or any headless instances!")
1191
+ return None
1192
+
1193
+ if force is not None and force not in available:
1194
+ _l.critical("DecLib was unable to force the decompiler you requested... please check your environment.")
1195
+ return None
1196
+
1197
+ if force is None:
1198
+ return available.pop()
1199
+
1200
+ if force in available:
1201
+ return force
1202
+
1203
+ return None
1204
+
1205
+ @staticmethod
1206
+ def discover(
1207
+ force_decompiler: str = None,
1208
+ interface_overrides: Optional[Dict[str, "DecompilerInterface"]] = None,
1209
+ **interface_kwargs
1210
+ ) -> Optional["DecompilerInterface"]:
1211
+ """
1212
+ This function is a special API helper that will attempt to detect the decompiler it is running in and
1213
+ return the valid DLController for that decompiler. You may also force the chosen deci.
1214
+
1215
+ @param force_decompiler: The optional string used to force a specific decompiler interface
1216
+ @param interface_overrides: The optional dict used to override the class of a decompiler interface
1217
+ @return: The DecompilerInterface associated with the current decompiler env
1218
+ """
1219
+ if force_decompiler and force_decompiler not in SUPPORTED_DECOMPILERS:
1220
+ raise ValueError(f"Unsupported decompiler {force_decompiler}")
1221
+
1222
+ if force_decompiler:
1223
+ if force_decompiler not in SUPPORTED_DECOMPILERS:
1224
+ raise ValueError(f"Unsupported decompiler {force_decompiler}, please use one of {SUPPORTED_DECOMPILERS}")
1225
+ current_decompiler = force_decompiler
1226
+ else:
1227
+ current_decompiler = DecompilerInterface.find_current_decompiler(force=force_decompiler)
1228
+
1229
+ # `project_dir` is a user-facing kwarg that translates to the
1230
+ # backend-specific cache/project location. Backends without a concept
1231
+ # of this simply ignore it.
1232
+ project_dir = interface_kwargs.pop("project_dir", None)
1233
+
1234
+ if current_decompiler == IDA_DECOMPILER:
1235
+ from declib.decompilers.ida.interface import IDAInterface
1236
+ deci_class = IDAInterface
1237
+ extra_kwargs = {}
1238
+ if project_dir:
1239
+ extra_kwargs["project_dir"] = project_dir
1240
+ elif current_decompiler == BINJA_DECOMPILER:
1241
+ from declib.decompilers.binja.interface import BinjaInterface
1242
+ deci_class = BinjaInterface
1243
+ extra_kwargs = {"bv": DecompilerInterface._find_global_in_call_frames('bv')}
1244
+ elif current_decompiler == ANGR_DECOMPILER:
1245
+ from declib.decompilers.angr.interface import AngrInterface
1246
+ deci_class = AngrInterface
1247
+ extra_kwargs = {"workspace": DecompilerInterface._find_global_in_call_frames('workspace')}
1248
+ elif current_decompiler == GHIDRA_DECOMPILER:
1249
+ from declib.decompilers.ghidra.interface import GhidraDecompilerInterface
1250
+ deci_class = GhidraDecompilerInterface
1251
+ extra_kwargs = {"flat_api": DecompilerInterface._find_global_in_call_frames('__this__')}
1252
+ if project_dir:
1253
+ extra_kwargs["project_location"] = project_dir
1254
+ else:
1255
+ raise ValueError("Please use DecLib with our supported decompiler set!")
1256
+
1257
+ if interface_overrides is not None and current_decompiler in interface_overrides:
1258
+ deci_class = interface_overrides[current_decompiler]
1259
+
1260
+ interface_kwargs.update(extra_kwargs)
1261
+ return deci_class(**interface_kwargs)