declib 3.8.0__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-3.8.0/LICENSE +24 -0
  2. declib-3.8.0/PKG-INFO +138 -0
  3. declib-3.8.0/README.md +105 -0
  4. declib-3.8.0/declib/__init__.py +9 -0
  5. declib-3.8.0/declib/__main__.py +190 -0
  6. declib-3.8.0/declib/api/__init__.py +13 -0
  7. declib-3.8.0/declib/api/artifact_dict.py +153 -0
  8. declib-3.8.0/declib/api/artifact_lifter.py +161 -0
  9. declib-3.8.0/declib/api/decompiler_client.py +1219 -0
  10. declib-3.8.0/declib/api/decompiler_interface.py +1261 -0
  11. declib-3.8.0/declib/api/decompiler_server.py +782 -0
  12. declib-3.8.0/declib/api/server_registry.py +171 -0
  13. declib-3.8.0/declib/api/type_definition_parser.py +201 -0
  14. declib-3.8.0/declib/api/type_parser.py +409 -0
  15. declib-3.8.0/declib/api/utils.py +31 -0
  16. declib-3.8.0/declib/artifacts/__init__.py +93 -0
  17. declib-3.8.0/declib/artifacts/artifact.py +311 -0
  18. declib-3.8.0/declib/artifacts/comment.py +49 -0
  19. declib-3.8.0/declib/artifacts/context.py +61 -0
  20. declib-3.8.0/declib/artifacts/decompilation.py +35 -0
  21. declib-3.8.0/declib/artifacts/enum.py +53 -0
  22. declib-3.8.0/declib/artifacts/formatting.py +27 -0
  23. declib-3.8.0/declib/artifacts/func.py +433 -0
  24. declib-3.8.0/declib/artifacts/global_variable.py +31 -0
  25. declib-3.8.0/declib/artifacts/patch.py +49 -0
  26. declib-3.8.0/declib/artifacts/segment.py +37 -0
  27. declib-3.8.0/declib/artifacts/stack_variable.py +50 -0
  28. declib-3.8.0/declib/artifacts/struct.py +184 -0
  29. declib-3.8.0/declib/artifacts/typedef.py +59 -0
  30. declib-3.8.0/declib/cli/__init__.py +3 -0
  31. declib-3.8.0/declib/cli/decompiler_cli.py +1487 -0
  32. declib-3.8.0/declib/configuration.py +184 -0
  33. declib-3.8.0/declib/decompiler_stubs/__init__.py +0 -0
  34. declib-3.8.0/declib/decompiler_stubs/angr_declib/__init__.py +4 -0
  35. declib-3.8.0/declib/decompiler_stubs/binja_declib/__init__.py +4 -0
  36. declib-3.8.0/declib/decompiler_stubs/ida_declib.py +8 -0
  37. declib-3.8.0/declib/decompilers/__init__.py +8 -0
  38. declib-3.8.0/declib/decompilers/angr/__init__.py +11 -0
  39. declib-3.8.0/declib/decompilers/angr/artifact_lifter.py +46 -0
  40. declib-3.8.0/declib/decompilers/angr/compat.py +262 -0
  41. declib-3.8.0/declib/decompilers/angr/interface.py +949 -0
  42. declib-3.8.0/declib/decompilers/binja/__init__.py +0 -0
  43. declib-3.8.0/declib/decompilers/binja/artifact_lifter.py +32 -0
  44. declib-3.8.0/declib/decompilers/binja/hooks.py +201 -0
  45. declib-3.8.0/declib/decompilers/binja/interface.py +795 -0
  46. declib-3.8.0/declib/decompilers/ghidra/__init__.py +0 -0
  47. declib-3.8.0/declib/decompilers/ghidra/artifact_lifter.py +60 -0
  48. declib-3.8.0/declib/decompilers/ghidra/compat/__init__.py +0 -0
  49. declib-3.8.0/declib/decompilers/ghidra/compat/headless.py +156 -0
  50. declib-3.8.0/declib/decompilers/ghidra/compat/imports.py +78 -0
  51. declib-3.8.0/declib/decompilers/ghidra/compat/state.py +54 -0
  52. declib-3.8.0/declib/decompilers/ghidra/compat/transaction.py +30 -0
  53. declib-3.8.0/declib/decompilers/ghidra/hooks.py +242 -0
  54. declib-3.8.0/declib/decompilers/ghidra/interface.py +1433 -0
  55. declib-3.8.0/declib/decompilers/ida/__init__.py +0 -0
  56. declib-3.8.0/declib/decompilers/ida/artifact_lifter.py +51 -0
  57. declib-3.8.0/declib/decompilers/ida/compat.py +2054 -0
  58. declib-3.8.0/declib/decompilers/ida/hooks.py +700 -0
  59. declib-3.8.0/declib/decompilers/ida/ida_ui.py +80 -0
  60. declib-3.8.0/declib/decompilers/ida/interface.py +659 -0
  61. declib-3.8.0/declib/logger.py +101 -0
  62. declib-3.8.0/declib/plugin_installer.py +259 -0
  63. declib-3.8.0/declib/skills/__init__.py +24 -0
  64. declib-3.8.0/declib/skills/decompiler/SKILL.md +316 -0
  65. declib-3.8.0/declib/ui/__init__.py +33 -0
  66. declib-3.8.0/declib/ui/qt_objects.py +146 -0
  67. declib-3.8.0/declib/ui/utils.py +115 -0
  68. declib-3.8.0/declib/ui/version.py +14 -0
  69. declib-3.8.0/declib.egg-info/PKG-INFO +138 -0
  70. declib-3.8.0/declib.egg-info/SOURCES.txt +79 -0
  71. declib-3.8.0/declib.egg-info/dependency_links.txt +1 -0
  72. declib-3.8.0/declib.egg-info/entry_points.txt +3 -0
  73. declib-3.8.0/declib.egg-info/requires.txt +20 -0
  74. declib-3.8.0/declib.egg-info/top_level.txt +1 -0
  75. declib-3.8.0/pyproject.toml +62 -0
  76. declib-3.8.0/setup.cfg +4 -0
  77. declib-3.8.0/tests/test_artifacts.py +199 -0
  78. declib-3.8.0/tests/test_cli.py +89 -0
  79. declib-3.8.0/tests/test_client_server.py +703 -0
  80. declib-3.8.0/tests/test_decompiler_cli.py +1078 -0
  81. declib-3.8.0/tests/test_decompilers.py +922 -0
declib-3.8.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2023, BinSync
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
declib-3.8.0/PKG-INFO ADDED
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: declib
3
+ Version: 3.8.0
4
+ Summary: Your Only Decompiler API Lib - A generic API to script in and out of decompilers
5
+ License: BSD 2 Clause
6
+ Project-URL: Homepage, https://github.com/binsync/declib
7
+ Classifier: License :: OSI Approved :: BSD License
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: toml
15
+ Requires-Dist: ply
16
+ Requires-Dist: pycparser~=3.0
17
+ Requires-Dist: setuptools
18
+ Requires-Dist: prompt_toolkit
19
+ Requires-Dist: tqdm
20
+ Requires-Dist: psutil
21
+ Requires-Dist: pyghidra
22
+ Requires-Dist: platformdirs
23
+ Requires-Dist: filelock
24
+ Requires-Dist: networkx
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest; extra == "test"
27
+ Requires-Dist: angr; extra == "test"
28
+ Requires-Dist: requests; extra == "test"
29
+ Requires-Dist: ipdb; extra == "test"
30
+ Provides-Extra: ghidra
31
+ Requires-Dist: PySide6-Essentials!=6.7.0,>=6.4.2; extra == "ghidra"
32
+ Dynamic: license-file
33
+
34
+ # DecLib
35
+ The decompiler API that works everywhere!
36
+
37
+ DecLib is an abstracted decompiler API that enables you to write plugins/scripts that work, with minimal edit,
38
+ in every decompiler supported by DecLib. DecLib was originally designed to work with [BinSync](https://binsync.net), and is the backbone
39
+ for all BinSync based plugins.
40
+ As an example, with the same script, you can [redefine the types of function variables with custom structs](./examples/struct_and_variable_use.py), all in less
41
+ than 30 lines, in any supported decompilers.
42
+
43
+ ## Install
44
+ ```bash
45
+ pip install declib
46
+ ```
47
+
48
+ The minimum Python version is **3.10**.
49
+
50
+ ## Supported Decompilers
51
+ - IDA Pro: **>= 8.4** (if you have an older version, use `v1.26.0`)
52
+ - Binary Ninja: **>= 2.4**
53
+ - angr-management: **>= 9.0**
54
+ - Ghidra: **>= 12.0** (started in PyGhidra mode)
55
+
56
+ ## Usage
57
+ DecLib exposes all decompiler API through the abstract class `DecompilerInterface`. The `DecompilerInterface`
58
+ can be used in either the default mode, which assumes a GUI, or `headless` mode. In `headless` mode, the interface will
59
+ start a new process using a specified decompiler.
60
+
61
+ You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
62
+ more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
63
+
64
+ If you want a simplified command line interface (especially well-suited for LLMs), see the
65
+ [`decompiler` CLI guide](./docs/decompiler_cli.md).
66
+
67
+ ### UI Mode (default)
68
+ To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
69
+ auto find the correct interface. Copy the below code into any supported decompiler and it should run without edit.
70
+
71
+ ```python
72
+ from declib.api import DecompilerInterface
73
+
74
+ deci = DecompilerInterface.discover()
75
+ for addr in deci.functions:
76
+ function = deci.functions[addr]
77
+ if function.header.type == "void":
78
+ function.header.type = "int"
79
+ deci.functions[function.addr] = function
80
+ ```
81
+
82
+ Note that for Ghidra in UI mode you must first start it in PyGhidra mode. You can do this by going to your install dir
83
+ and running `./support/pyghidraRun`.
84
+
85
+ ### Headless Mode
86
+ To use headless mode you must specify a decompiler to use. You can get the traditional interface using the following:
87
+
88
+ ```python
89
+ from declib.api import DecompilerInterface
90
+
91
+ deci = DecompilerInterface.discover(force_decompiler="ghidra", headless=True)
92
+ ```
93
+
94
+ In the case of Ghidra, you must have the environment variable `GHIDRA_INSTALL_DIR` set to the path of the Ghidra
95
+ installation (the place the `ghidraRun` script is located).
96
+
97
+ ### Artifact Access Caveats
98
+ In designing the dictionaries that contain all Artifacts in a decompiler, we had a clash between ease-of-use and speed.
99
+ When accessing some artifacts like a `Function`, we must decompile the function. Decompiling is slow. Due to this issue
100
+ we slightly changed how these dictionaries work to fast accessing.
101
+
102
+ The only way to access a **full** artifact is to use the `getitem` interface of a dictionary. In practice this
103
+ looks like the following:
104
+ ```python
105
+ for func_addr, light_func in deci.functions.items():
106
+ full_function = deci.function[func_addr]
107
+ ```
108
+
109
+ Notice, when using the `items` function the function is `light`, meaning it does not contain stack vars and other
110
+ info. This also means using `keys`, `values`, or `list` on an artifact dictionary will have the same affect.
111
+
112
+ ### Serializing Artifacts
113
+ All artifacts are serializable to the TOML and JSON formats. Serialization is done like so:
114
+ ```python
115
+ from declib.artifacts import Function
116
+ import json
117
+
118
+ my_func = Function(name="my_func", addr=0x4000, size=0x10)
119
+ json_str = my_func.dumps(fmt="json")
120
+ loaded_dict = json.loads(json_str) # now loadable through normal JSON parsing
121
+ loaded_func = Function.loads(json_str, fmt="json")
122
+ ```
123
+
124
+ ## Sponsors
125
+ BinSync and its associated projects would not be possible without sponsorship.
126
+ In no particular order, we'd like to thank all the organizations that have previously or are currently sponsoring
127
+ one of the many BinSync projects.
128
+
129
+ <p align="center">
130
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/nsf.png?raw=true" alt="NSF" style="height: 100px; display: inline-block; vertical-align: middle; margin-right: 40px;">
131
+ <br>
132
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/darpa.png?raw=true" alt="DARPA" style="height: 70px; display: inline-block; vertical-align: middle; margin-right: 40px;">
133
+ <br>
134
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/arpah.svg?raw=true" alt="ARPA-H" style="height: 50px; display: inline-block; vertical-align: middle; margin-right: 40px;">
135
+ <br>
136
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/reveng_ai.svg?raw=true" alt="RevEng AI" style="height: 50px; display: inline-block; vertical-align: middle;">
137
+ </p>
138
+
declib-3.8.0/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # DecLib
2
+ The decompiler API that works everywhere!
3
+
4
+ DecLib is an abstracted decompiler API that enables you to write plugins/scripts that work, with minimal edit,
5
+ in every decompiler supported by DecLib. DecLib was originally designed to work with [BinSync](https://binsync.net), and is the backbone
6
+ for all BinSync based plugins.
7
+ As an example, with the same script, you can [redefine the types of function variables with custom structs](./examples/struct_and_variable_use.py), all in less
8
+ than 30 lines, in any supported decompilers.
9
+
10
+ ## Install
11
+ ```bash
12
+ pip install declib
13
+ ```
14
+
15
+ The minimum Python version is **3.10**.
16
+
17
+ ## Supported Decompilers
18
+ - IDA Pro: **>= 8.4** (if you have an older version, use `v1.26.0`)
19
+ - Binary Ninja: **>= 2.4**
20
+ - angr-management: **>= 9.0**
21
+ - Ghidra: **>= 12.0** (started in PyGhidra mode)
22
+
23
+ ## Usage
24
+ DecLib exposes all decompiler API through the abstract class `DecompilerInterface`. The `DecompilerInterface`
25
+ can be used in either the default mode, which assumes a GUI, or `headless` mode. In `headless` mode, the interface will
26
+ start a new process using a specified decompiler.
27
+
28
+ You can find various examples using DecLib in the [examples](./examples) folder. Examples that are plugins show off
29
+ more of the complicated API that allows you to use an abstracted UI, artifacts, and more.
30
+
31
+ If you want a simplified command line interface (especially well-suited for LLMs), see the
32
+ [`decompiler` CLI guide](./docs/decompiler_cli.md).
33
+
34
+ ### UI Mode (default)
35
+ To use the same script everywhere, use the convenience function `DecompilerInterface.discover_interface()`, which will
36
+ auto find the correct interface. Copy the below code into any supported decompiler and it should run without edit.
37
+
38
+ ```python
39
+ from declib.api import DecompilerInterface
40
+
41
+ deci = DecompilerInterface.discover()
42
+ for addr in deci.functions:
43
+ function = deci.functions[addr]
44
+ if function.header.type == "void":
45
+ function.header.type = "int"
46
+ deci.functions[function.addr] = function
47
+ ```
48
+
49
+ Note that for Ghidra in UI mode you must first start it in PyGhidra mode. You can do this by going to your install dir
50
+ and running `./support/pyghidraRun`.
51
+
52
+ ### Headless Mode
53
+ To use headless mode you must specify a decompiler to use. You can get the traditional interface using the following:
54
+
55
+ ```python
56
+ from declib.api import DecompilerInterface
57
+
58
+ deci = DecompilerInterface.discover(force_decompiler="ghidra", headless=True)
59
+ ```
60
+
61
+ In the case of Ghidra, you must have the environment variable `GHIDRA_INSTALL_DIR` set to the path of the Ghidra
62
+ installation (the place the `ghidraRun` script is located).
63
+
64
+ ### Artifact Access Caveats
65
+ In designing the dictionaries that contain all Artifacts in a decompiler, we had a clash between ease-of-use and speed.
66
+ When accessing some artifacts like a `Function`, we must decompile the function. Decompiling is slow. Due to this issue
67
+ we slightly changed how these dictionaries work to fast accessing.
68
+
69
+ The only way to access a **full** artifact is to use the `getitem` interface of a dictionary. In practice this
70
+ looks like the following:
71
+ ```python
72
+ for func_addr, light_func in deci.functions.items():
73
+ full_function = deci.function[func_addr]
74
+ ```
75
+
76
+ Notice, when using the `items` function the function is `light`, meaning it does not contain stack vars and other
77
+ info. This also means using `keys`, `values`, or `list` on an artifact dictionary will have the same affect.
78
+
79
+ ### Serializing Artifacts
80
+ All artifacts are serializable to the TOML and JSON formats. Serialization is done like so:
81
+ ```python
82
+ from declib.artifacts import Function
83
+ import json
84
+
85
+ my_func = Function(name="my_func", addr=0x4000, size=0x10)
86
+ json_str = my_func.dumps(fmt="json")
87
+ loaded_dict = json.loads(json_str) # now loadable through normal JSON parsing
88
+ loaded_func = Function.loads(json_str, fmt="json")
89
+ ```
90
+
91
+ ## Sponsors
92
+ BinSync and its associated projects would not be possible without sponsorship.
93
+ In no particular order, we'd like to thank all the organizations that have previously or are currently sponsoring
94
+ one of the many BinSync projects.
95
+
96
+ <p align="center">
97
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/nsf.png?raw=true" alt="NSF" style="height: 100px; display: inline-block; vertical-align: middle; margin-right: 40px;">
98
+ <br>
99
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/darpa.png?raw=true" alt="DARPA" style="height: 70px; display: inline-block; vertical-align: middle; margin-right: 40px;">
100
+ <br>
101
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/arpah.svg?raw=true" alt="ARPA-H" style="height: 50px; display: inline-block; vertical-align: middle; margin-right: 40px;">
102
+ <br>
103
+ <img src="https://github.com/binsync/binsync/blob/main/assets/images/sponsors/reveng_ai.svg?raw=true" alt="RevEng AI" style="height: 50px; display: inline-block; vertical-align: middle;">
104
+ </p>
105
+
@@ -0,0 +1,9 @@
1
+ __version__ = "3.8.0"
2
+
3
+
4
+ import logging
5
+ logging.getLogger("declib").addHandler(logging.NullHandler())
6
+ from declib.logger import Loggers
7
+ loggers = Loggers()
8
+ del Loggers
9
+ del logging
@@ -0,0 +1,190 @@
1
+ import argparse
2
+ import sys
3
+ import logging
4
+
5
+ from declib.plugin_installer import DecLibPluginInstaller
6
+
7
+ _l = logging.getLogger(__name__)
8
+
9
+
10
+ def install():
11
+ DecLibPluginInstaller().install()
12
+
13
+
14
+ def start_server(
15
+ socket_path=None, decompiler=None, binary_path=None, headless=False,
16
+ server_id=None, project_dir=None,
17
+ ):
18
+ """Start the DecompilerServer (AF_UNIX socket-based)"""
19
+ try:
20
+ from declib.api.decompiler_server import DecompilerServer
21
+ from declib.api.decompiler_interface import DecompilerInterface
22
+
23
+ # Configure logging
24
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
25
+
26
+ # Prepare interface kwargs
27
+ interface_kwargs = {}
28
+ if decompiler:
29
+ interface_kwargs['force_decompiler'] = decompiler
30
+ if binary_path:
31
+ interface_kwargs['binary_path'] = binary_path
32
+ if headless:
33
+ interface_kwargs['headless'] = headless
34
+ if project_dir:
35
+ interface_kwargs['project_dir'] = project_dir
36
+
37
+ # Create and start server
38
+ if socket_path:
39
+ _l.info(f"Starting AF_UNIX DecompilerServer on {socket_path}")
40
+ else:
41
+ _l.info("Starting AF_UNIX DecompilerServer with auto-generated socket path")
42
+ if interface_kwargs:
43
+ _l.info(f"Interface options: {interface_kwargs}")
44
+
45
+ with DecompilerServer(socket_path=socket_path, server_id=server_id, **interface_kwargs) as server:
46
+ _l.info("Server started successfully. Press Ctrl+C to stop.")
47
+ _l.info("Connect with: DecompilerClient.discover('unix://{}')".format(server.socket_path))
48
+ try:
49
+ server.wait_for_shutdown()
50
+ except KeyboardInterrupt:
51
+ _l.info("Shutting down server...")
52
+
53
+ except ImportError as e:
54
+ _l.error(f"Failed to import required modules: {e}")
55
+ sys.exit(1)
56
+ except Exception as e:
57
+ _l.error(f"Failed to start server: {e}")
58
+ sys.exit(1)
59
+
60
+
61
+ def test_client(server_url=None):
62
+ """Test the DecompilerClient connection"""
63
+ try:
64
+ from declib.api.decompiler_client import DecompilerClient
65
+
66
+ # Configure logging
67
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
68
+
69
+ if server_url:
70
+ _l.info(f"Testing connection to DecompilerServer at {server_url}")
71
+ else:
72
+ _l.info("Testing connection to auto-discovered DecompilerServer")
73
+
74
+ with DecompilerClient.discover(server_url=server_url) as client:
75
+ _l.info(f"Successfully connected to {client.name} decompiler")
76
+ _l.info(f"Binary path: {client.binary_path}")
77
+ _l.info(f"Binary hash: {client.binary_hash}")
78
+ _l.info(f"Decompiler available: {client.decompiler_available}")
79
+
80
+ # Test fast artifact collections (benchmark performance)
81
+ import time
82
+ start_time = time.time()
83
+ functions = list(client.functions.items())
84
+ end_time = time.time()
85
+ _l.info(f"Retrieved {len(functions)} functions in {end_time - start_time:.3f}s")
86
+
87
+ start_time = time.time()
88
+ comments = list(client.comments.keys())
89
+ end_time = time.time()
90
+ _l.info(f"Retrieved {len(comments)} comment keys in {end_time - start_time:.3f}s")
91
+
92
+ _l.info("✅ Client test completed successfully!")
93
+
94
+ except ImportError as e:
95
+ _l.error(f"Failed to import required modules: {e}")
96
+ sys.exit(1)
97
+ except Exception as e:
98
+ _l.error(f"Client test failed: {e}")
99
+ sys.exit(1)
100
+
101
+
102
+ def main():
103
+ parser = argparse.ArgumentParser(
104
+ description="""
105
+ The DecLib Command Line Util. This is the script interface to DecLib that allows you to install and run
106
+ the Ghidra UI for running plugins, and start the DecompilerServer.
107
+ """,
108
+ epilog="""
109
+ Examples:
110
+ declib --install |
111
+ declib --server --socket-path /tmp/my_server.sock |
112
+ declib --server --decompiler ghidra --binary-path /path/to/binary --headless
113
+ """
114
+ )
115
+ parser.add_argument(
116
+ "--install", action="store_true", help="""
117
+ Install all the DecLib plugins to every decompiler.
118
+ """
119
+ )
120
+ parser.add_argument(
121
+ "--single-decompiler-install", nargs=2, metavar=('decompiler', 'path'), help="Install DAILA into a single decompiler. Decompiler must be one of: ida, ghidra, binja, angr."
122
+ )
123
+ parser.add_argument(
124
+ "--server", action="store_true", help="""
125
+ Start the DecompilerServer to expose DecompilerInterface APIs over AF_UNIX sockets.
126
+ """
127
+ )
128
+ parser.add_argument(
129
+ "--server-url", help="""
130
+ URL of the DecompilerServer to connect to (e.g., unix:///tmp/server.sock).
131
+ If not specified, will auto-discover running servers.
132
+ """
133
+ )
134
+ parser.add_argument(
135
+ "--socket-path", help="""
136
+ Path for the AF_UNIX socket (default: auto-generated in temp directory).
137
+ """
138
+ )
139
+ parser.add_argument(
140
+ "--decompiler", choices=["ida", "ghidra", "binja", "angr"], help="""
141
+ Force a specific decompiler for the server. If not specified, auto-detection will be used.
142
+ """
143
+ )
144
+ parser.add_argument(
145
+ "--binary-path", help="""
146
+ Path to the binary file to analyze (required for headless mode).
147
+ """
148
+ )
149
+ parser.add_argument(
150
+ "--headless", action="store_true", help="""
151
+ Run the decompiler in headless mode (no GUI). Requires --binary-path.
152
+ """
153
+ )
154
+ parser.add_argument(
155
+ "--server-id", help="""
156
+ Explicit server ID to use; if omitted, a unique one is generated.
157
+ """
158
+ )
159
+ parser.add_argument(
160
+ "--project-dir", help="""
161
+ Directory where the backend should store its project/database files
162
+ (Ghidra project, IDA .id*, etc.). If omitted, backend defaults apply
163
+ (Ghidra creates a project next to the binary; IDA writes .id* next
164
+ to the binary).
165
+ """
166
+ )
167
+ args = parser.parse_args()
168
+
169
+ if args.single_decompiler_install:
170
+ decompiler, path = args.single_decompiler_install
171
+ DecLibPluginInstaller().install(interactive=False, paths_by_target={decompiler: path})
172
+ elif args.install:
173
+ install()
174
+ elif args.server:
175
+ if args.headless and not args.binary_path:
176
+ parser.error("--headless requires --binary-path")
177
+ start_server(
178
+ socket_path=args.socket_path,
179
+ decompiler=args.decompiler,
180
+ binary_path=args.binary_path,
181
+ headless=args.headless,
182
+ server_id=args.server_id,
183
+ project_dir=args.project_dir,
184
+ )
185
+ else:
186
+ parser.print_help()
187
+
188
+
189
+ if __name__ == "__main__":
190
+ main()
@@ -0,0 +1,13 @@
1
+ from .artifact_lifter import ArtifactLifter
2
+ from .decompiler_interface import DecompilerInterface
3
+ from .type_parser import CTypeParser, CType
4
+
5
+ from .decompiler_interface import (
6
+ DecompilerInterface
7
+ )
8
+ from .artifact_lifter import (
9
+ ArtifactLifter
10
+ )
11
+ from .type_parser import (
12
+ CTypeParser, CType
13
+ )
@@ -0,0 +1,153 @@
1
+ import typing
2
+ import logging
3
+
4
+ from declib.artifacts import (
5
+ Artifact, Comment, Enum, FunctionHeader, Function, FunctionArgument,
6
+ GlobalVariable, Patch, Segment, StackVariable, Struct, StructMember, Typedef
7
+ )
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from declib.api import DecompilerInterface
11
+
12
+ _l = logging.getLogger(__name__)
13
+
14
+
15
+ class ArtifactDict(dict):
16
+ """
17
+ The ArtifactDict is a Dictionary wrapper around the getting/setting/listing of artifacts in the decompiler. This
18
+ allows for a more pythonic interface to the decompiler artifacts. For example, instead of doing:
19
+ deci._set_function(func)
20
+
21
+ You can do:
22
+ >>> deci.functions[func.addr] = func
23
+
24
+ This class is not meant to be instantiated directly, but rather through the DecompilerInterface class.
25
+ There is currently some interesting affects and caveats to using this class:
26
+ - When you list artifacts, by calling list(), you will get a light copy of the artifacts. This means that if you
27
+ modify the artifacts in the list, they will not be reflected in the decompiler. You also do need get current
28
+ data in the decompiler, only an acknowledgement that the artifact exists.
29
+ - You must reassign the artifact to the dictionary to update the decompiler.
30
+ - When assigning something to the dictionary, it must always be in its lifted form. You will also only get lifted
31
+ artifacts back from the dictionary.
32
+ - For convience, you can access functions by their lowered address
33
+ """
34
+
35
+ def __init__(self, artifact_cls, deci: "DecompilerInterface", error_on_duplicate=False, scopable=False):
36
+ super().__init__()
37
+
38
+ self._deci = deci
39
+ self._error_on_duplicate = error_on_duplicate
40
+ self._scopable = scopable
41
+ self._art_function = {
42
+ # ArtifactType: (setter, getter, lister)
43
+ Function: (self._deci._set_function, self._deci._get_function, self._deci._functions, self._deci._del_function),
44
+ StackVariable: (self._deci._set_stack_variable, self._deci._get_stack_variable, self._deci._stack_variables, self._deci._del_stack_variable),
45
+ GlobalVariable: (self._deci._set_global_variable, self._deci._get_global_var, self._deci._global_vars, self._deci._del_global_var),
46
+ Struct: (self._deci._set_struct, self._deci._get_struct, self._deci._structs, self._deci._del_struct),
47
+ Enum: (self._deci._set_enum, self._deci._get_enum, self._deci._enums, self._deci._del_enum),
48
+ Typedef: (self._deci._set_typedef, self._deci._get_typedef, self._deci._typedefs, self._deci._del_typedef),
49
+ Comment: (self._deci._set_comment, self._deci._get_comment, self._deci._comments, self._deci._del_comment),
50
+ Patch: (self._deci._set_patch, self._deci._get_patch, self._deci._patches, self._deci._del_patch),
51
+ Segment: (self._deci._set_segment, self._deci._get_segment, self._deci._segments, self._deci._del_segment)
52
+ }
53
+
54
+ functions = self._art_function.get(artifact_cls, None)
55
+ if functions is None:
56
+ raise ValueError(f"Attempting to create a dict for a Artifact class that is not supported: {artifact_cls}")
57
+
58
+ self._artifact_class = artifact_cls
59
+ self._artifact_setter, self._artifact_getter, self._artifact_lister, self._artifact_remover = functions
60
+
61
+ def __len__(self):
62
+ return len(self._artifact_lister())
63
+
64
+ def _lifted_art_lister(self):
65
+ d = self._artifact_lister()
66
+ d_items = list(d.items())
67
+ if not d_items:
68
+ return {}
69
+
70
+ is_addr = hasattr(d_items[0][1], "addr")
71
+ new_d = {}
72
+ for k, v in d_items:
73
+ if is_addr:
74
+ k = self._deci.art_lifter.lift_addr(k)
75
+ new_d[k] = self._deci.art_lifter.lift(v)
76
+
77
+ return new_d
78
+
79
+ def __getitem__(self, item):
80
+ """
81
+ Takes a lifted identifier as input and returns a lifted artifact
82
+ """
83
+ if isinstance(item, int):
84
+ item = self._deci.art_lifter.lower_addr(item)
85
+ if self._scopable and not self._deci.supports_type_scopes:
86
+ item, _ = self._deci.art_lifter.parse_scoped_type(item)
87
+
88
+ art = self._artifact_getter(item)
89
+ if art is None:
90
+ raise KeyError
91
+
92
+ return self._deci.art_lifter.lift(art)
93
+
94
+ def __setitem__(self, key, value):
95
+ """
96
+ Both key and value must be lifted artifacts
97
+ """
98
+ if not isinstance(value, self._artifact_class):
99
+ raise ValueError(f"Attempting to set a value of type {type(value)} to a dict of type {self._artifact_class}")
100
+
101
+ if isinstance(key, int):
102
+ key = self._deci.art_lifter.lower_addr(key)
103
+ if self._scopable and not self._deci.supports_type_scopes:
104
+ key, _ = self._deci.art_lifter.parse_scoped_type(key)
105
+
106
+ art = self._deci.art_lifter.lower(value)
107
+ if not self._artifact_setter(art) and self._error_on_duplicate:
108
+ raise ValueError(f"Set value {value} is already present at key {key}")
109
+
110
+ def __contains__(self, item):
111
+ if isinstance(item, int):
112
+ item = self._deci.art_lifter.lower_addr(item)
113
+ if self._scopable and not self._deci.supports_type_scopes:
114
+ item, _ = self._deci.art_lifter.parse_scoped_type(item)
115
+
116
+ data = self._artifact_getter(item)
117
+ return data is not None
118
+
119
+ def __delitem__(self, key):
120
+ if isinstance(key, int):
121
+ key = self._deci.art_lifter.lower_addr(key)
122
+ if self._scopable and not self._deci.supports_type_scopes:
123
+ key, _ = self._deci.art_lifter.parse_scoped_type(key)
124
+
125
+ art = self._artifact_getter(key)
126
+ if isinstance(art, Struct):
127
+ self._artifact_remover(key)
128
+ self._deci.struct_changed(art, deleted=True)
129
+ else:
130
+ self._artifact_remover(key)
131
+
132
+ def __iter__(self):
133
+ return iter(self._lifted_art_lister())
134
+
135
+ def __repr__(self):
136
+ return f"<{self.__class__.__name__}: {self._artifact_class.__name__} len={self.__len__()}>"
137
+
138
+ def __str__(self):
139
+ return f"{self._lifted_art_lister()}"
140
+
141
+ def keys(self):
142
+ return self._lifted_art_lister().keys()
143
+
144
+ def values(self):
145
+ return self._lifted_art_lister().values()
146
+
147
+ def items(self):
148
+ return self._lifted_art_lister().items()
149
+
150
+ def get(self, key, default=None):
151
+ if key in self:
152
+ return self[key]
153
+ return default