unidecompiler-cli 0.2.2__tar.gz → 0.2.3__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.
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/PKG-INFO +20 -2
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/README.md +17 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/pyproject.toml +3 -2
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli/cli.py +6 -1
- unidecompiler_cli-0.2.3/src/unidecompiler_cli/symbolic.py +148 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/PKG-INFO +20 -2
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/SOURCES.txt +1 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/requires.txt +2 -1
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/setup.cfg +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli/__init__.py +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli/input_sources.py +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli/simulation.py +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli/templates.py +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/dependency_links.txt +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/entry_points.txt +0 -0
- {unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unidecompiler-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Command-line host for unidecompiler plugins
|
|
5
5
|
Author-email: Wker <1670133844@qq.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -11,7 +11,8 @@ Requires-Python: >=3.11
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: unidecompiler<0.3.0,>=0.2.2
|
|
13
13
|
Requires-Dist: unidecompiler-export<0.3.0,>=0.2.2
|
|
14
|
-
Requires-Dist: unidecompiler-simulator<0.3.0,>=0.2.
|
|
14
|
+
Requires-Dist: unidecompiler-simulator<0.3.0,>=0.2.3
|
|
15
|
+
Requires-Dist: unidecompiler-symbolic<0.3.0,>=0.2.3
|
|
15
16
|
Requires-Dist: unidecompiler-simulation-host-python<0.3.0,>=0.2.2
|
|
16
17
|
|
|
17
18
|
# unidecompiler-cli
|
|
@@ -111,6 +112,23 @@ separate simulator library:
|
|
|
111
112
|
unidecompiler simulate sample.bytecode --function 'Example.run' --args '[1, 2]'
|
|
112
113
|
```
|
|
113
114
|
|
|
115
|
+
Bounded symbolic execution uses the same opaque frontend query and explores
|
|
116
|
+
the recovered generic IR only. `--symbolic` names scalar symbolic parameters;
|
|
117
|
+
all other parameters are supplied through `--concrete`:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
unidecompiler symbolic sample.pyc --function add \
|
|
121
|
+
--symbolic '{"left":{"sort":"int"}}' --concrete '{"right":5}'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Use `--format text` for a human-readable summary. Exploration is bounded by
|
|
125
|
+
`--max-paths`, `--max-steps`, `--max-loop-unroll`, `--max-call-depth`, and
|
|
126
|
+
`--solver-timeout-ms`; symbolic sorts are `bool`, `int`, `real`, and `bitvec`
|
|
127
|
+
with a positive `bit_width`. JSON output contains each path's constraints,
|
|
128
|
+
model, return/raise value, and CFG trace. Non-success statuses such as
|
|
129
|
+
`unsupported`, `solver_timeout`, limits, `cancelled`, and `invalid_request`
|
|
130
|
+
are returned explicitly and produce a non-zero exit code.
|
|
131
|
+
|
|
114
132
|
For trusted programs that require functions outside the lifted module, pass a
|
|
115
133
|
Python environment file. Top-level functions are matched by name, while their
|
|
116
134
|
stdout and stderr are returned as structured simulation events:
|
|
@@ -95,6 +95,23 @@ separate simulator library:
|
|
|
95
95
|
unidecompiler simulate sample.bytecode --function 'Example.run' --args '[1, 2]'
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
Bounded symbolic execution uses the same opaque frontend query and explores
|
|
99
|
+
the recovered generic IR only. `--symbolic` names scalar symbolic parameters;
|
|
100
|
+
all other parameters are supplied through `--concrete`:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
unidecompiler symbolic sample.pyc --function add \
|
|
104
|
+
--symbolic '{"left":{"sort":"int"}}' --concrete '{"right":5}'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use `--format text` for a human-readable summary. Exploration is bounded by
|
|
108
|
+
`--max-paths`, `--max-steps`, `--max-loop-unroll`, `--max-call-depth`, and
|
|
109
|
+
`--solver-timeout-ms`; symbolic sorts are `bool`, `int`, `real`, and `bitvec`
|
|
110
|
+
with a positive `bit_width`. JSON output contains each path's constraints,
|
|
111
|
+
model, return/raise value, and CFG trace. Non-success statuses such as
|
|
112
|
+
`unsupported`, `solver_timeout`, limits, `cancelled`, and `invalid_request`
|
|
113
|
+
are returned explicitly and produce a non-zero exit code.
|
|
114
|
+
|
|
98
115
|
For trusted programs that require functions outside the lifted module, pass a
|
|
99
116
|
Python environment file. Top-level functions are matched by name, while their
|
|
100
117
|
stdout and stderr are returned as structured simulation events:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unidecompiler-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Command-line host for unidecompiler plugins"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
@@ -13,7 +13,8 @@ requires-python = ">=3.11"
|
|
|
13
13
|
dependencies = [
|
|
14
14
|
"unidecompiler>=0.2.2,<0.3.0",
|
|
15
15
|
"unidecompiler-export>=0.2.2,<0.3.0",
|
|
16
|
-
"unidecompiler-simulator>=0.2.
|
|
16
|
+
"unidecompiler-simulator>=0.2.3,<0.3.0",
|
|
17
|
+
"unidecompiler-symbolic>=0.2.3,<0.3.0",
|
|
17
18
|
"unidecompiler-simulation-host-python>=0.2.2,<0.3.0",
|
|
18
19
|
]
|
|
19
20
|
|
|
@@ -24,11 +24,15 @@ from unidecompiler.progress import ProgressEvent, ProgressReporter
|
|
|
24
24
|
|
|
25
25
|
def main(argv: list[str] | None = None, *, registry: FrontendRegistry | None = None) -> int:
|
|
26
26
|
command_argv = sys.argv[1:] if argv is None else argv
|
|
27
|
-
if command_argv and command_argv[0] in {"simulate", "template", "export-template"}:
|
|
27
|
+
if command_argv and command_argv[0] in {"simulate", "symbolic", "template", "export-template"}:
|
|
28
28
|
if command_argv[0] in {"template", "export-template"}:
|
|
29
29
|
from unidecompiler_cli.templates import main as template_main
|
|
30
30
|
|
|
31
31
|
return template_main(command_argv[1:])
|
|
32
|
+
if command_argv[0] == "symbolic":
|
|
33
|
+
from unidecompiler_cli.symbolic import main as symbolic_main
|
|
34
|
+
|
|
35
|
+
return symbolic_main(command_argv[1:], registry=registry)
|
|
32
36
|
from unidecompiler_cli.simulation import main as simulate_main
|
|
33
37
|
|
|
34
38
|
return simulate_main(command_argv[1:], registry=registry)
|
|
@@ -40,6 +44,7 @@ def main(argv: list[str] | None = None, *, registry: FrontendRegistry | None = N
|
|
|
40
44
|
" template export a VM frontend or GUI plugin starter project\n"
|
|
41
45
|
" export-template alias for template\n"
|
|
42
46
|
" simulate run a recovered generic-IR function\n\n"
|
|
47
|
+
" symbolic explore recovered generic-IR paths\n\n"
|
|
43
48
|
"Template examples:\n"
|
|
44
49
|
" unidecompiler template frontend MyVM -o ./my-vm --author NAME "
|
|
45
50
|
"--description TEXT --requirements TEXT --suffix .vm --version 1\n"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Symbolic execution command hosted by the unidecompiler CLI package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import z3
|
|
12
|
+
|
|
13
|
+
from unidecompiler.plugin_registry import FrontendRegistry
|
|
14
|
+
from unidecompiler_symbolic import (
|
|
15
|
+
SymbolicEngine,
|
|
16
|
+
SymbolicInput,
|
|
17
|
+
SymbolicLimits,
|
|
18
|
+
SymbolicSort,
|
|
19
|
+
SymbolicStatus,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: list[str] | None = None, *, registry: FrontendRegistry | None = None) -> int:
|
|
24
|
+
parser = argparse.ArgumentParser(prog="unidecompiler symbolic")
|
|
25
|
+
parser.add_argument("input")
|
|
26
|
+
parser.add_argument("--function", required=True, help="frontend-owned function query")
|
|
27
|
+
parser.add_argument("--frontend")
|
|
28
|
+
parser.add_argument("--symbolic", default="{}", help="JSON object of symbolic input specifications")
|
|
29
|
+
parser.add_argument("--concrete", default="{}", help="JSON object of concrete parameter values")
|
|
30
|
+
parser.add_argument("--max-paths", type=int, default=256)
|
|
31
|
+
parser.add_argument("--max-steps", type=int, default=100_000)
|
|
32
|
+
parser.add_argument("--max-loop-unroll", type=int, default=32)
|
|
33
|
+
parser.add_argument("--max-call-depth", type=int, default=32)
|
|
34
|
+
parser.add_argument("--solver-timeout-ms", type=int, default=5_000)
|
|
35
|
+
parser.add_argument("--format", choices=("json", "text"), default="json")
|
|
36
|
+
args = parser.parse_args(argv)
|
|
37
|
+
try:
|
|
38
|
+
symbolic_inputs = _symbolic_inputs(args.symbolic)
|
|
39
|
+
concrete_args = _object(args.concrete, "--concrete")
|
|
40
|
+
limits = SymbolicLimits(
|
|
41
|
+
args.max_paths, args.max_steps, args.max_loop_unroll,
|
|
42
|
+
args.max_call_depth, args.solver_timeout_ms,
|
|
43
|
+
)
|
|
44
|
+
limits.validate()
|
|
45
|
+
engine = SymbolicEngine.from_registry(registry or FrontendRegistry.discover())
|
|
46
|
+
result = engine.explore_path(
|
|
47
|
+
Path(args.input), args.function, frontend_id=args.frontend,
|
|
48
|
+
symbolic_inputs=symbolic_inputs, concrete_args=concrete_args, limits=limits,
|
|
49
|
+
)
|
|
50
|
+
except (OSError, ValueError, json.JSONDecodeError) as error:
|
|
51
|
+
print(f"invalid symbolic request: {error}", file=sys.stderr)
|
|
52
|
+
return 2
|
|
53
|
+
if args.format == "text":
|
|
54
|
+
print(_text_result(result))
|
|
55
|
+
else:
|
|
56
|
+
print(json.dumps(_result_payload(result), ensure_ascii=False, sort_keys=True, allow_nan=False))
|
|
57
|
+
return _exit_code(result.status)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _object(value: str, option: str) -> dict[str, Any]:
|
|
61
|
+
parsed = json.loads(value)
|
|
62
|
+
if not isinstance(parsed, dict) or not all(isinstance(key, str) for key in parsed):
|
|
63
|
+
raise ValueError(f"{option} must be a JSON object with string keys")
|
|
64
|
+
return parsed
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _symbolic_inputs(value: str) -> tuple[SymbolicInput, ...]:
|
|
68
|
+
result: list[SymbolicInput] = []
|
|
69
|
+
for name, spec in _object(value, "--symbolic").items():
|
|
70
|
+
if not isinstance(spec, dict):
|
|
71
|
+
raise ValueError("each --symbolic input must be an object")
|
|
72
|
+
unknown = set(spec) - {"sort", "bit_width"}
|
|
73
|
+
if unknown:
|
|
74
|
+
raise ValueError(f"unknown symbolic input fields for {name!r}: {', '.join(sorted(unknown))}")
|
|
75
|
+
try:
|
|
76
|
+
sort = SymbolicSort(spec.get("sort", SymbolicSort.INT.value))
|
|
77
|
+
except ValueError as error:
|
|
78
|
+
raise ValueError(f"invalid symbolic sort for {name!r}") from error
|
|
79
|
+
width = spec.get("bit_width")
|
|
80
|
+
if width is not None and (not isinstance(width, int) or isinstance(width, bool)):
|
|
81
|
+
raise ValueError(f"bit_width for {name!r} must be an integer")
|
|
82
|
+
item = SymbolicInput(name, sort, width)
|
|
83
|
+
item.validate()
|
|
84
|
+
result.append(item)
|
|
85
|
+
return tuple(result)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _value(value: Any) -> Any:
|
|
89
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
90
|
+
return value
|
|
91
|
+
if z3.is_expr(value):
|
|
92
|
+
return str(value)
|
|
93
|
+
if isinstance(value, tuple):
|
|
94
|
+
return [_value(item) for item in value]
|
|
95
|
+
if isinstance(value, list):
|
|
96
|
+
return [_value(item) for item in value]
|
|
97
|
+
if isinstance(value, dict):
|
|
98
|
+
return {str(key): _value(item) for key, item in value.items()}
|
|
99
|
+
return repr(value)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _result_payload(result) -> dict[str, Any]:
|
|
103
|
+
return {
|
|
104
|
+
"schema_version": 1,
|
|
105
|
+
"status": result.status.value,
|
|
106
|
+
"explored_paths": result.explored_paths,
|
|
107
|
+
"pruned_paths": result.pruned_paths,
|
|
108
|
+
"diagnostic": result.diagnostic,
|
|
109
|
+
"paths": [
|
|
110
|
+
{
|
|
111
|
+
"path_id": path.path_id,
|
|
112
|
+
"status": path.status.value,
|
|
113
|
+
"constraints": [str(item) for item in path.constraints],
|
|
114
|
+
"model": None if path.model is None else {key: _value(value) for key, value in path.model.items()},
|
|
115
|
+
"returns": [_value(value) for value in path.returns],
|
|
116
|
+
"raised": _value(path.raised),
|
|
117
|
+
"blocks": list(path.blocks),
|
|
118
|
+
"edges": list(path.edges),
|
|
119
|
+
"steps": path.steps,
|
|
120
|
+
"diagnostic": path.diagnostic,
|
|
121
|
+
}
|
|
122
|
+
for path in result.paths
|
|
123
|
+
],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _text_result(result) -> str:
|
|
128
|
+
lines = [
|
|
129
|
+
f"{result.status.value}: {result.explored_paths} explored, {result.pruned_paths} pruned",
|
|
130
|
+
]
|
|
131
|
+
if result.diagnostic:
|
|
132
|
+
lines.append(result.diagnostic)
|
|
133
|
+
for path in result.paths:
|
|
134
|
+
outcome = path.diagnostic or repr(path.raised if path.raised is not None else path.returns)
|
|
135
|
+
lines.append(f"[{path.path_id}] {path.status.value}; {path.steps} steps; {outcome}")
|
|
136
|
+
if path.constraints:
|
|
137
|
+
lines.append(" constraints: " + "; ".join(str(item) for item in path.constraints))
|
|
138
|
+
if path.model:
|
|
139
|
+
lines.append(" model: " + json.dumps({key: _value(value) for key, value in path.model.items()}, sort_keys=True))
|
|
140
|
+
return "\n".join(lines)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _exit_code(status: SymbolicStatus) -> int:
|
|
144
|
+
if status is SymbolicStatus.COMPLETED:
|
|
145
|
+
return 0
|
|
146
|
+
if status is SymbolicStatus.INVALID_REQUEST:
|
|
147
|
+
return 2
|
|
148
|
+
return 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unidecompiler-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Command-line host for unidecompiler plugins
|
|
5
5
|
Author-email: Wker <1670133844@qq.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -11,7 +11,8 @@ Requires-Python: >=3.11
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: unidecompiler<0.3.0,>=0.2.2
|
|
13
13
|
Requires-Dist: unidecompiler-export<0.3.0,>=0.2.2
|
|
14
|
-
Requires-Dist: unidecompiler-simulator<0.3.0,>=0.2.
|
|
14
|
+
Requires-Dist: unidecompiler-simulator<0.3.0,>=0.2.3
|
|
15
|
+
Requires-Dist: unidecompiler-symbolic<0.3.0,>=0.2.3
|
|
15
16
|
Requires-Dist: unidecompiler-simulation-host-python<0.3.0,>=0.2.2
|
|
16
17
|
|
|
17
18
|
# unidecompiler-cli
|
|
@@ -111,6 +112,23 @@ separate simulator library:
|
|
|
111
112
|
unidecompiler simulate sample.bytecode --function 'Example.run' --args '[1, 2]'
|
|
112
113
|
```
|
|
113
114
|
|
|
115
|
+
Bounded symbolic execution uses the same opaque frontend query and explores
|
|
116
|
+
the recovered generic IR only. `--symbolic` names scalar symbolic parameters;
|
|
117
|
+
all other parameters are supplied through `--concrete`:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
unidecompiler symbolic sample.pyc --function add \
|
|
121
|
+
--symbolic '{"left":{"sort":"int"}}' --concrete '{"right":5}'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Use `--format text` for a human-readable summary. Exploration is bounded by
|
|
125
|
+
`--max-paths`, `--max-steps`, `--max-loop-unroll`, `--max-call-depth`, and
|
|
126
|
+
`--solver-timeout-ms`; symbolic sorts are `bool`, `int`, `real`, and `bitvec`
|
|
127
|
+
with a positive `bit_width`. JSON output contains each path's constraints,
|
|
128
|
+
model, return/raise value, and CFG trace. Non-success statuses such as
|
|
129
|
+
`unsupported`, `solver_timeout`, limits, `cancelled`, and `invalid_request`
|
|
130
|
+
are returned explicitly and produce a non-zero exit code.
|
|
131
|
+
|
|
114
132
|
For trusted programs that require functions outside the lifted module, pass a
|
|
115
133
|
Python environment file. Top-level functions are matched by name, while their
|
|
116
134
|
stdout and stderr are returned as structured simulation events:
|
{unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -4,6 +4,7 @@ src/unidecompiler_cli/__init__.py
|
|
|
4
4
|
src/unidecompiler_cli/cli.py
|
|
5
5
|
src/unidecompiler_cli/input_sources.py
|
|
6
6
|
src/unidecompiler_cli/simulation.py
|
|
7
|
+
src/unidecompiler_cli/symbolic.py
|
|
7
8
|
src/unidecompiler_cli/templates.py
|
|
8
9
|
src/unidecompiler_cli.egg-info/PKG-INFO
|
|
9
10
|
src/unidecompiler_cli.egg-info/SOURCES.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{unidecompiler_cli-0.2.2 → unidecompiler_cli-0.2.3}/src/unidecompiler_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|