declib 4.0.0__tar.gz → 4.0.2__tar.gz

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 (81) hide show
  1. {declib-4.0.0 → declib-4.0.2}/PKG-INFO +9 -2
  2. {declib-4.0.0 → declib-4.0.2}/README.md +8 -1
  3. {declib-4.0.0 → declib-4.0.2}/declib/__init__.py +1 -1
  4. {declib-4.0.0 → declib-4.0.2}/declib/api/decompiler_client.py +3 -3
  5. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/angr/interface.py +12 -2
  6. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/interface.py +5 -1
  7. {declib-4.0.0 → declib-4.0.2}/declib/plugin_installer.py +1 -1
  8. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/PKG-INFO +9 -2
  9. {declib-4.0.0 → declib-4.0.2}/pyproject.toml +1 -1
  10. {declib-4.0.0 → declib-4.0.2}/tests/test_client_server.py +23 -1
  11. {declib-4.0.0 → declib-4.0.2}/tests/test_decompilers.py +40 -1
  12. {declib-4.0.0 → declib-4.0.2}/LICENSE +0 -0
  13. {declib-4.0.0 → declib-4.0.2}/declib/__main__.py +0 -0
  14. {declib-4.0.0 → declib-4.0.2}/declib/api/__init__.py +0 -0
  15. {declib-4.0.0 → declib-4.0.2}/declib/api/artifact_dict.py +0 -0
  16. {declib-4.0.0 → declib-4.0.2}/declib/api/artifact_lifter.py +0 -0
  17. {declib-4.0.0 → declib-4.0.2}/declib/api/decompiler_interface.py +0 -0
  18. {declib-4.0.0 → declib-4.0.2}/declib/api/decompiler_server.py +0 -0
  19. {declib-4.0.0 → declib-4.0.2}/declib/api/server_registry.py +0 -0
  20. {declib-4.0.0 → declib-4.0.2}/declib/api/type_definition_parser.py +0 -0
  21. {declib-4.0.0 → declib-4.0.2}/declib/api/type_parser.py +0 -0
  22. {declib-4.0.0 → declib-4.0.2}/declib/api/utils.py +0 -0
  23. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/__init__.py +0 -0
  24. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/artifact.py +0 -0
  25. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/comment.py +0 -0
  26. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/context.py +0 -0
  27. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/decompilation.py +0 -0
  28. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/enum.py +0 -0
  29. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/formatting.py +0 -0
  30. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/func.py +0 -0
  31. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/global_variable.py +0 -0
  32. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/patch.py +0 -0
  33. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/segment.py +0 -0
  34. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/stack_variable.py +0 -0
  35. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/struct.py +0 -0
  36. {declib-4.0.0 → declib-4.0.2}/declib/artifacts/typedef.py +0 -0
  37. {declib-4.0.0 → declib-4.0.2}/declib/cli/__init__.py +0 -0
  38. {declib-4.0.0 → declib-4.0.2}/declib/cli/decompiler_cli.py +0 -0
  39. {declib-4.0.0 → declib-4.0.2}/declib/configuration.py +0 -0
  40. {declib-4.0.0 → declib-4.0.2}/declib/decompiler_stubs/__init__.py +0 -0
  41. {declib-4.0.0 → declib-4.0.2}/declib/decompiler_stubs/angr_declib/__init__.py +0 -0
  42. {declib-4.0.0 → declib-4.0.2}/declib/decompiler_stubs/binja_declib/__init__.py +0 -0
  43. {declib-4.0.0 → declib-4.0.2}/declib/decompiler_stubs/ida_declib.py +0 -0
  44. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/__init__.py +0 -0
  45. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/angr/__init__.py +0 -0
  46. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/angr/artifact_lifter.py +0 -0
  47. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/angr/compat.py +0 -0
  48. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/binja/__init__.py +0 -0
  49. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/binja/artifact_lifter.py +0 -0
  50. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/binja/hooks.py +0 -0
  51. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/binja/interface.py +0 -0
  52. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/__init__.py +0 -0
  53. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/artifact_lifter.py +0 -0
  54. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/compat/__init__.py +0 -0
  55. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/compat/headless.py +0 -0
  56. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/compat/imports.py +0 -0
  57. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/compat/state.py +0 -0
  58. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/compat/transaction.py +0 -0
  59. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ghidra/hooks.py +0 -0
  60. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/__init__.py +0 -0
  61. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/artifact_lifter.py +0 -0
  62. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/compat.py +0 -0
  63. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/hooks.py +0 -0
  64. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/ida_ui.py +0 -0
  65. {declib-4.0.0 → declib-4.0.2}/declib/decompilers/ida/interface.py +0 -0
  66. {declib-4.0.0 → declib-4.0.2}/declib/logger.py +0 -0
  67. {declib-4.0.0 → declib-4.0.2}/declib/skills/__init__.py +0 -0
  68. {declib-4.0.0 → declib-4.0.2}/declib/skills/decompiler/SKILL.md +0 -0
  69. {declib-4.0.0 → declib-4.0.2}/declib/ui/__init__.py +0 -0
  70. {declib-4.0.0 → declib-4.0.2}/declib/ui/qt_objects.py +0 -0
  71. {declib-4.0.0 → declib-4.0.2}/declib/ui/utils.py +0 -0
  72. {declib-4.0.0 → declib-4.0.2}/declib/ui/version.py +0 -0
  73. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/SOURCES.txt +0 -0
  74. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/dependency_links.txt +0 -0
  75. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/entry_points.txt +0 -0
  76. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/requires.txt +0 -0
  77. {declib-4.0.0 → declib-4.0.2}/declib.egg-info/top_level.txt +0 -0
  78. {declib-4.0.0 → declib-4.0.2}/setup.cfg +0 -0
  79. {declib-4.0.0 → declib-4.0.2}/tests/test_artifacts.py +0 -0
  80. {declib-4.0.0 → declib-4.0.2}/tests/test_cli.py +0 -0
  81. {declib-4.0.0 → declib-4.0.2}/tests/test_decompiler_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: declib
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Your Only Decompiler API Lib - A generic API to script in and out of decompilers
5
5
  License: BSD 2 Clause
6
6
  Project-URL: Homepage, https://github.com/binsync/declib
@@ -61,8 +61,15 @@ start a new process using a specified decompiler.
61
61
  You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
62
62
  more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
63
63
 
64
+ ### Agent CLI
65
+
64
66
  If you want a simplified command line interface (especially well-suited for LLMs), see the
65
- [`decompiler` CLI guide](./docs/decompiler_cli.md).
67
+ [decompiler CLI guide](./docs/decompiler_cli.md).
68
+
69
+ ```
70
+ decompiler load /path/to/binary --backend ida
71
+ decompiler decompile main --id 1234
72
+ ```
66
73
 
67
74
  ### UI Mode (default)
68
75
  To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
@@ -28,8 +28,15 @@ start a new process using a specified decompiler.
28
28
  You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
29
29
  more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
30
30
 
31
+ ### Agent CLI
32
+
31
33
  If you want a simplified command line interface (especially well-suited for LLMs), see the
32
- [`decompiler` CLI guide](./docs/decompiler_cli.md).
34
+ [decompiler CLI guide](./docs/decompiler_cli.md).
35
+
36
+ ```
37
+ decompiler load /path/to/binary --backend ida
38
+ decompiler decompile main --id 1234
39
+ ```
33
40
 
34
41
  ### UI Mode (default)
35
42
  To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
@@ -1,4 +1,4 @@
1
- __version__ = "4.0.0"
1
+ __version__ = "4.0.2"
2
2
 
3
3
 
4
4
  import logging
@@ -792,14 +792,14 @@ class DecompilerClient:
792
792
  _l.info("DecompilerClient shut down complete")
793
793
 
794
794
  def shutdown_server(self) -> None:
795
- """Ask the server to tear down its decompiler interface, then disconnect.
795
+ """Ask the server to stop, then disconnect.
796
796
 
797
797
  Used by CLI commands like ``decompiler stop``. Regular usage should
798
798
  prefer :meth:`shutdown`, which leaves the server running.
799
799
  """
800
800
  if self._socket:
801
801
  try:
802
- self._send_request({"type": "shutdown_deci"})
802
+ self._send_request({"type": "shutdown_server"})
803
803
  except Exception:
804
804
  pass
805
805
  self.shutdown()
@@ -1216,4 +1216,4 @@ class DecompilerClient:
1216
1216
  callback(gvar, **kwargs)
1217
1217
  except Exception as e:
1218
1218
  _l.error(f"Error in global variable change callback: {e}")
1219
- return gvar
1219
+ return gvar
@@ -847,7 +847,13 @@ class AngrInterface(DecompilerInterface):
847
847
 
848
848
  @staticmethod
849
849
  def line_map_from_decompilation(dec):
850
- import ailment
850
+ try:
851
+ # angr >= 9.2.2xx vendors ailment; the standalone `ailment` package
852
+ # is a different module whose Label class fails isinstance checks
853
+ # against statements produced by modern angr.
854
+ from angr import ailment
855
+ except ImportError:
856
+ import ailment
851
857
  from angr.analyses.decompiler.structured_codegen.c import CStructuredCodeWalker, CFunctionCall, CIfElse, CIfBreak
852
858
 
853
859
  if dec is None or dec.codegen is None:
@@ -874,7 +880,11 @@ class AngrInterface(DecompilerInterface):
874
880
  except Exception:
875
881
  continue
876
882
 
877
- ail_block_stmts = [stmt for stmt in ail_block.statements if not isinstance(stmt, ailment.statement.Label)]
883
+ ail_block_stmts = [
884
+ stmt for stmt in ail_block.statements
885
+ if not isinstance(stmt, ailment.statement.Label)
886
+ and getattr(stmt, "ins_addr", None) is not None
887
+ ]
878
888
  if not ail_block_stmts:
879
889
  continue
880
890
 
@@ -34,6 +34,10 @@ _l = logging.getLogger(__name__)
34
34
 
35
35
  class GhidraDecompilerInterface(DecompilerInterface):
36
36
  CACHE_TIMEOUT = 5
37
+ # GUI-backed Ghidra program APIs are not safe to enter concurrently from
38
+ # arbitrary socket worker threads. DecompilerServer routes calls through
39
+ # its main-thread dispatcher when this is enabled.
40
+ requires_main_thread_dispatch = True
37
41
  _program: Optional["Program"]
38
42
  flat_api: "FlatProgramAPI"
39
43
 
@@ -76,6 +80,7 @@ class GhidraDecompilerInterface(DecompilerInterface):
76
80
  # main thread queue
77
81
  self._main_thread_queue = queue.Queue()
78
82
  self._results_queue = queue.Queue()
83
+ self.requires_main_thread_dispatch = not kwargs.get("headless", False)
79
84
 
80
85
  super().__init__(
81
86
  name="ghidra",
@@ -1430,4 +1435,3 @@ class GhidraDecompilerInterface(DecompilerInterface):
1430
1435
  [code_unit for code_unit in self.currentProgram.getListing().getCodeUnits(func.getBody(), True)]
1431
1436
  for func in self.currentProgram.getFunctionManager().getFunctions(True)
1432
1437
  ]
1433
-
@@ -111,7 +111,7 @@ class PluginInstaller:
111
111
  - Path: install succeeded
112
112
  """
113
113
 
114
- PluginInstaller.info(f"Install for {target}? [y/n]")
114
+ PluginInstaller.info(f"Install for {target}? [y/N]")
115
115
  res = prompt("")
116
116
  if res.lower() != "y":
117
117
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: declib
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Your Only Decompiler API Lib - A generic API to script in and out of decompilers
5
5
  License: BSD 2 Clause
6
6
  Project-URL: Homepage, https://github.com/binsync/declib
@@ -61,8 +61,15 @@ start a new process using a specified decompiler.
61
61
  You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
62
62
  more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
63
63
 
64
+ ### Agent CLI
65
+
64
66
  If you want a simplified command line interface (especially well-suited for LLMs), see the
65
- [`decompiler` CLI guide](./docs/decompiler_cli.md).
67
+ [decompiler CLI guide](./docs/decompiler_cli.md).
68
+
69
+ ```
70
+ decompiler load /path/to/binary --backend ida
71
+ decompiler decompile main --id 1234
72
+ ```
66
73
 
67
74
  ### UI Mode (default)
68
75
  To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
@@ -59,4 +59,4 @@ license-files = ["LICENSE"]
59
59
  find = {namespaces = false}
60
60
 
61
61
  [tool.setuptools.dynamic]
62
- version = {attr = "declib.__version__"}
62
+ version = {attr = "declib.__version__"}
@@ -5,6 +5,7 @@ import tempfile
5
5
  import threading
6
6
  import time
7
7
  import unittest
8
+ from types import MethodType
8
9
  from pathlib import Path
9
10
 
10
11
  from declib.api.decompiler_server import DecompilerServer
@@ -217,6 +218,27 @@ class TestClientServer(unittest.TestCase):
217
218
  # Test KeyError handling for non-existent function
218
219
  with self.assertRaises(KeyError, msg="Should raise KeyError for non-existent function"):
219
220
  self.client.functions[0xDEADBEEF] # Non-existent function
221
+
222
+ def test_shutdown_server_requests_server_stop(self):
223
+ """Test that shutdown_server asks the remote server loop to stop."""
224
+ sent_requests = []
225
+ client = object.__new__(DecompilerClient)
226
+ client._socket = object()
227
+ client._event_listener_running = False
228
+
229
+ def fake_send_request(self, request):
230
+ sent_requests.append(request)
231
+
232
+ def fake_shutdown(self):
233
+ sent_requests.append({"type": "client_shutdown"})
234
+
235
+ client._send_request = MethodType(fake_send_request, client)
236
+ client.shutdown = MethodType(fake_shutdown, client)
237
+
238
+ client.shutdown_server()
239
+
240
+ self.assertEqual(sent_requests[0], {"type": "shutdown_server"})
241
+ self.assertEqual(sent_requests[1], {"type": "client_shutdown"})
220
242
 
221
243
  def test_client_context_manager(self):
222
244
  """Test client context manager functionality"""
@@ -700,4 +722,4 @@ class TestClientServer(unittest.TestCase):
700
722
 
701
723
 
702
724
  if __name__ == "__main__":
703
- unittest.main()
725
+ unittest.main()
@@ -1,14 +1,18 @@
1
1
  import json
2
2
  import logging
3
3
  import subprocess
4
+ import sys
4
5
  import tempfile
5
6
  import time
7
+ import types
6
8
  import unittest
9
+ from unittest.mock import MagicMock, patch
7
10
  from pathlib import Path
8
11
  from collections import defaultdict
9
12
  import os
10
13
 
11
14
  from declib.api import DecompilerInterface
15
+ from declib.api.decompiler_server import DecompilerServer
12
16
  from declib.artifacts import FunctionHeader, StackVariable, Struct, GlobalVariable, Enum, Comment, ArtifactFormat, \
13
17
  Decompilation, Function, StructMember, Typedef, Segment
14
18
  from declib.decompilers import IDA_DECOMPILER, ANGR_DECOMPILER, BINJA_DECOMPILER, GHIDRA_DECOMPILER
@@ -65,6 +69,41 @@ class TestHeadlessInterfaces(unittest.TestCase):
65
69
  if self.deci is not None:
66
70
  self.deci.shutdown()
67
71
 
72
+ def test_ghidra_server_dispatch_requirement(self):
73
+ import declib.decompilers.ghidra as ghidra_package
74
+
75
+ pyghidra = types.ModuleType("pyghidra")
76
+ pyghidra_core = types.ModuleType("pyghidra.core")
77
+ pyghidra_core._analyze_program = MagicMock()
78
+ pyghidra_core._get_language = MagicMock()
79
+ pyghidra_core._get_compiler_spec = MagicMock()
80
+ jpype = types.ModuleType("jpype")
81
+ jpype.JClass = type
82
+
83
+ modules = {
84
+ "pyghidra": pyghidra,
85
+ "pyghidra.core": pyghidra_core,
86
+ "jpype": jpype,
87
+ }
88
+ with patch.object(ghidra_package, "interface", create=True):
89
+ with patch.dict(sys.modules, modules):
90
+ sys.modules.pop("declib.decompilers.ghidra.interface", None)
91
+ from declib.decompilers.ghidra.interface import GhidraDecompilerInterface
92
+
93
+ with patch.object(GhidraDecompilerInterface, "_init_gui_components"), \
94
+ patch.object(GhidraDecompilerInterface, "_init_headless_components"):
95
+ for headless, expected in ((True, False), (False, True)):
96
+ with self.subTest(headless=headless):
97
+ deci = GhidraDecompilerInterface(
98
+ headless=headless,
99
+ config=MagicMock(),
100
+ )
101
+ server = object.__new__(DecompilerServer)
102
+ server.deci = deci
103
+
104
+ self.assertIs(deci.requires_main_thread_dispatch, expected)
105
+ self.assertIs(server.requires_main_thread, expected)
106
+
68
107
  def test_readme_example(self):
69
108
  # TODO: add angr
70
109
  for dec_name in [IDA_DECOMPILER, GHIDRA_DECOMPILER, BINJA_DECOMPILER]:
@@ -919,4 +958,4 @@ class TestHeadlessInterfaces(unittest.TestCase):
919
958
  deci.shutdown()
920
959
 
921
960
  if __name__ == "__main__":
922
- unittest.main()
961
+ unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes