pcode-graph 1.0.0__tar.gz → 1.1.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.
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/.github/workflows/release.yml +1 -1
- pcode_graph-1.1.0/.github/workflows/test.yml +17 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/PKG-INFO +2 -2
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/analysis.py +3 -2
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/charac.py +22 -5
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/checker.py +4 -4
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/cli.py +20 -2
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/emulator.py +3 -2
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/graph.py +52 -39
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/lief_importer.py +1 -1
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/maker.py +32 -15
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/simplifier.py +83 -72
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pyproject.toml +1 -1
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/maker_test.py +11 -11
- pcode_graph-1.1.0/todo.md +3 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/.gitignore +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/LICENSE +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/README.md +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/__init__.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/__main__.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/arch.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/asm.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/gnn_exporter.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/log.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/nx_exporter.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/pcode.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/registers.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/torch_utils.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/translator.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/utils.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/view.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/pcode_graph/visitor.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/requirements.txt +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/__init__.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/analysis_test.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/charac_test.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/conftest.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/emulator_test.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/exporter_test.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/fixtures.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/helpers.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/translator_test.py +0 -0
- {pcode_graph-1.0.0 → pcode_graph-1.1.0}/tests/utils_test.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_call:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
pytest:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-python@v5
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
- run: pip install .[test,torch]
|
|
17
|
+
- run: python -m pytest tests
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: pcode_graph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Build, display and vectorize semantic graphs from binary code
|
|
5
5
|
Project-URL: Homepage, https://github.com/quarkslab/pcode_graph
|
|
6
6
|
Project-URL: Issues, https://github.com/quarkslab/pcode_graph/issues
|
|
@@ -3,7 +3,7 @@ from enum import Enum
|
|
|
3
3
|
from typing import Iterator, override
|
|
4
4
|
from pypcode import OpCode, PcodeOp, Varnode
|
|
5
5
|
from pcode_graph.arch import Arch
|
|
6
|
-
from pcode_graph.pcode import
|
|
6
|
+
from pcode_graph.pcode import dump_operation, from_native_opcode
|
|
7
7
|
from pcode_graph.visitor import PcodeVisitor
|
|
8
8
|
|
|
9
9
|
OpIndex = int
|
|
@@ -56,7 +56,8 @@ class Var:
|
|
|
56
56
|
def wrap(var: Varnode) -> Var:
|
|
57
57
|
match var.space.name:
|
|
58
58
|
case "register":
|
|
59
|
-
|
|
59
|
+
name = var.getRegisterName() or "UNKNOWN"
|
|
60
|
+
return Var(VarKinds.Register, var.size, var.offset, name)
|
|
60
61
|
case "unique":
|
|
61
62
|
return Var(VarKinds.Unique, var.size, var.offset, f"${var.offset}")
|
|
62
63
|
case "const":
|
|
@@ -11,13 +11,27 @@ from pcode_graph.pcode import OpCodes
|
|
|
11
11
|
from collections import deque
|
|
12
12
|
from itertools import chain
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
class Stats(NamedTuple):
|
|
15
16
|
data_only: int
|
|
16
17
|
control_only: int
|
|
17
18
|
both: int
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def
|
|
21
|
+
def count_edges(cdg: CDG) -> Stats:
|
|
22
|
+
|
|
23
|
+
data_only: int = 0
|
|
24
|
+
|
|
25
|
+
for edge in cdg.edges:
|
|
26
|
+
if edge.kind == EdgeKinds.Data:
|
|
27
|
+
data_only += 1
|
|
28
|
+
|
|
29
|
+
return Stats(data_only, len(cdg.edges) - data_only, len(cdg.edges))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_graph_diameters(cdg: CDG) -> Stats:
|
|
33
|
+
"""Computes the diameter of the given graph."""
|
|
34
|
+
|
|
21
35
|
n = len(cdg.nodes)
|
|
22
36
|
|
|
23
37
|
def build_adjacency(edges):
|
|
@@ -47,17 +61,19 @@ def get_graph_diameters(cdg: CDG) -> Diameters:
|
|
|
47
61
|
if dist[neigbor] == -1:
|
|
48
62
|
dist[neigbor] = dist[current] + 1
|
|
49
63
|
front.append(neigbor)
|
|
50
|
-
d = max(d, max(dist))
|
|
64
|
+
d = max(d, max(dist))
|
|
51
65
|
return d
|
|
52
66
|
|
|
53
|
-
return
|
|
67
|
+
return Stats(
|
|
54
68
|
diameter(build_adjacency(data_edges)),
|
|
55
69
|
diameter(build_adjacency(ctrl_edges)),
|
|
56
70
|
diameter(build_adjacency(chain(data_edges, ctrl_edges))),
|
|
57
71
|
)
|
|
58
72
|
|
|
73
|
+
|
|
59
74
|
class FunctionCharacteristics(IntFlag):
|
|
60
|
-
"""Helps evaluating the level of difficulty of
|
|
75
|
+
"""Helps evaluating the level of difficulty of a function,
|
|
76
|
+
regarding miscalleneous ML tasks."""
|
|
61
77
|
|
|
62
78
|
data = auto()
|
|
63
79
|
condition = auto()
|
|
@@ -68,6 +84,7 @@ class FunctionCharacteristics(IntFlag):
|
|
|
68
84
|
|
|
69
85
|
|
|
70
86
|
def get_function_characteristics(graph: CDG) -> FunctionCharacteristics:
|
|
87
|
+
"""Characterizes a function regarding control flow features."""
|
|
71
88
|
|
|
72
89
|
# Every function contains data-flow easiest level
|
|
73
90
|
function_characteristics = FunctionCharacteristics.data
|
|
@@ -34,8 +34,8 @@ def check_graph(graph: CDG):
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
proxy = proxy_dict[index]
|
|
37
|
-
control_succs = proxy.get_control_successors()
|
|
38
|
-
data_preds = proxy.get_data_predecessors()
|
|
37
|
+
control_succs = list(proxy.get_control_successors())
|
|
38
|
+
data_preds = list(proxy.get_data_predecessors())
|
|
39
39
|
|
|
40
40
|
if node.kind != NodeKinds.Operation:
|
|
41
41
|
assert node.opcode is None
|
|
@@ -46,11 +46,11 @@ def check_graph(graph: CDG):
|
|
|
46
46
|
|
|
47
47
|
data_numbers: list[int] = []
|
|
48
48
|
control_numbers: list[int] = []
|
|
49
|
-
for edge in proxy.
|
|
49
|
+
for edge in proxy.get_in_edges():
|
|
50
50
|
if edge.kind == EdgeKinds.Data:
|
|
51
51
|
assert edge.operand_number is not None
|
|
52
52
|
data_numbers.append(edge.operand_number)
|
|
53
|
-
for edge in proxy.
|
|
53
|
+
for edge in proxy.get_out_edges():
|
|
54
54
|
if edge.kind == EdgeKinds.Control:
|
|
55
55
|
assert edge.operand_number is not None
|
|
56
56
|
control_numbers.append(edge.operand_number)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Dumps Control and Data Graph (CDG) and various data from a binary or assembly file.
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
cdg ( asm | functions | html | md | pcode | table ) <input_path> [options]
|
|
6
|
+
cdg ( asm | functions | html | md | pcode | table | stats ) <input_path> [options]
|
|
7
7
|
|
|
8
8
|
Dump commands:
|
|
9
9
|
asm Assembly dump
|
|
@@ -12,6 +12,7 @@ Dump commands:
|
|
|
12
12
|
md CDG in markdown/mermaid format
|
|
13
13
|
pcode P-Code operations
|
|
14
14
|
table Markdown table of the P-Code enriched by static analysis results
|
|
15
|
+
stats Prints graph statistics
|
|
15
16
|
|
|
16
17
|
Options:
|
|
17
18
|
-f, --function <name> Take only the function with given symbol name.
|
|
@@ -21,10 +22,11 @@ Options:
|
|
|
21
22
|
-D, --dataflow-only Only extract dataflow graph (and no control flow).
|
|
22
23
|
-o, --output <path> Where to write the output.
|
|
23
24
|
-h, --help This help.
|
|
24
|
-
-d, --debug Debug mode: do no catch exceptions.
|
|
25
|
+
-d, --debug Debug mode: do no catch exceptions.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
28
|
from pathlib import Path
|
|
29
|
+
import re
|
|
28
30
|
import sys
|
|
29
31
|
from docopt import docopt
|
|
30
32
|
from lief import Binary
|
|
@@ -33,6 +35,7 @@ from loguru import logger
|
|
|
33
35
|
import pypcode
|
|
34
36
|
from pcode_graph.arch import Arch
|
|
35
37
|
from pcode_graph.asm import Assembler
|
|
38
|
+
from pcode_graph.charac import count_edges, get_graph_diameters
|
|
36
39
|
from pcode_graph.lief_importer import (
|
|
37
40
|
get_function,
|
|
38
41
|
iter_code_sections,
|
|
@@ -104,6 +107,10 @@ def main():
|
|
|
104
107
|
code = function.content
|
|
105
108
|
else:
|
|
106
109
|
# Lift piece of code
|
|
110
|
+
if not re.search("[a-fA-FxX]", chunk_begin_end):
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"This does not look like hexadecimal addresses: {chunk_begin_end}"
|
|
113
|
+
)
|
|
107
114
|
start_address, end_address = [
|
|
108
115
|
int(a, 16) for a in chunk_begin_end.split(":")
|
|
109
116
|
]
|
|
@@ -164,6 +171,17 @@ def main():
|
|
|
164
171
|
print(str(cdg), file=output)
|
|
165
172
|
sys.exit(0)
|
|
166
173
|
|
|
174
|
+
if args["stats"]:
|
|
175
|
+
stats = dict(
|
|
176
|
+
diameter=get_graph_diameters(cdg)._asdict(),
|
|
177
|
+
edges=count_edges(cdg)._asdict(),
|
|
178
|
+
nodes=len(cdg.nodes),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
print(repr(stats), file=output)
|
|
182
|
+
sys.exit(0)
|
|
183
|
+
|
|
184
|
+
assert args["html"], "Unknown command"
|
|
167
185
|
assert output_path, "HTML output file is mandatory"
|
|
168
186
|
output.close()
|
|
169
187
|
draw_graph(cdg, Path(output_path))
|
|
@@ -69,7 +69,7 @@ class Emulator(PcodeVisitor):
|
|
|
69
69
|
OperandNumber | None, tuple[NodeIndex, Value]
|
|
70
70
|
] = {}
|
|
71
71
|
|
|
72
|
-
for in_edge in p.
|
|
72
|
+
for in_edge in p.get_in_edges():
|
|
73
73
|
if in_edge.kind != EdgeKinds.Data:
|
|
74
74
|
continue
|
|
75
75
|
|
|
@@ -88,7 +88,8 @@ class Emulator(PcodeVisitor):
|
|
|
88
88
|
propagated = True
|
|
89
89
|
|
|
90
90
|
case NodeKinds.OutputRegister:
|
|
91
|
-
(
|
|
91
|
+
(in_edge_index,) = p.get_input_edges()
|
|
92
|
+
in_edge = self.graph.edges[in_edge_index]
|
|
92
93
|
value = self.values.get(in_edge.source_node)
|
|
93
94
|
if value is not None:
|
|
94
95
|
self.values[index] = value
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from dataclasses import dataclass, replace
|
|
2
2
|
from enum import Enum, auto
|
|
3
|
+
from typing import Iterator
|
|
3
4
|
from pcode_graph.pcode import OpCodes
|
|
4
5
|
from pcode_graph.utils import unsigned_to_signed
|
|
5
6
|
|
|
6
7
|
NodeIndex = int
|
|
8
|
+
EdgeIndex = int
|
|
7
9
|
OperandNumber = int
|
|
8
10
|
RegisterId = int
|
|
9
11
|
Value = int
|
|
@@ -31,7 +33,7 @@ class EdgeKinds(Enum):
|
|
|
31
33
|
Control = 1
|
|
32
34
|
|
|
33
35
|
|
|
34
|
-
@dataclass
|
|
36
|
+
@dataclass(slots=True)
|
|
35
37
|
class Edge:
|
|
36
38
|
source_node: NodeIndex
|
|
37
39
|
|
|
@@ -44,7 +46,7 @@ class Edge:
|
|
|
44
46
|
operand_number: OperandNumber | None
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
@dataclass
|
|
49
|
+
@dataclass(slots=True)
|
|
48
50
|
class Node:
|
|
49
51
|
# Kind of node
|
|
50
52
|
kind: NodeKinds
|
|
@@ -99,43 +101,54 @@ class Node:
|
|
|
99
101
|
raise ValueError(self.kind)
|
|
100
102
|
|
|
101
103
|
|
|
102
|
-
@dataclass
|
|
104
|
+
@dataclass(slots=True)
|
|
103
105
|
class EdgeProxy:
|
|
104
106
|
"""Easy access to edges for one node."""
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
edge.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
graph: CDG
|
|
109
|
+
in_edge_indexes: list[EdgeIndex]
|
|
110
|
+
out_edge_indexes: list[EdgeIndex]
|
|
111
|
+
|
|
112
|
+
def get_in_edges(self) -> Iterator[Edge]:
|
|
113
|
+
for index in self.in_edge_indexes:
|
|
114
|
+
yield self.graph.edges[index]
|
|
115
|
+
|
|
116
|
+
def get_out_edges(self) -> Iterator[Edge]:
|
|
117
|
+
for index in self.out_edge_indexes:
|
|
118
|
+
yield self.graph.edges[index]
|
|
119
|
+
|
|
120
|
+
def get_data_predecessors(self) -> Iterator[NodeIndex]:
|
|
121
|
+
for e in self.in_edge_indexes:
|
|
122
|
+
edge = self.graph.edges[e]
|
|
123
|
+
if edge.kind == EdgeKinds.Data:
|
|
124
|
+
yield edge.source_node
|
|
125
|
+
|
|
126
|
+
def get_control_predecessors(self) -> Iterator[NodeIndex]:
|
|
127
|
+
for e in self.in_edge_indexes:
|
|
128
|
+
edge = self.graph.edges[e]
|
|
129
|
+
if edge.kind == EdgeKinds.Control:
|
|
130
|
+
yield edge.source_node
|
|
131
|
+
|
|
132
|
+
def get_data_successors(self) -> Iterator[NodeIndex]:
|
|
133
|
+
for e in self.out_edge_indexes:
|
|
134
|
+
edge = self.graph.edges[e]
|
|
135
|
+
if edge.kind == EdgeKinds.Data:
|
|
136
|
+
yield edge.destination_node
|
|
137
|
+
|
|
138
|
+
def get_control_successors(self) -> Iterator[NodeIndex]:
|
|
139
|
+
for e in self.out_edge_indexes:
|
|
140
|
+
edge = self.graph.edges[e]
|
|
141
|
+
if edge.kind == EdgeKinds.Control:
|
|
142
|
+
yield edge.destination_node
|
|
143
|
+
|
|
144
|
+
def get_input_edges(self) -> list[EdgeIndex]:
|
|
134
145
|
"""Returns data input operands, by number."""
|
|
135
|
-
|
|
146
|
+
|
|
147
|
+
operands: list[tuple[OperandNumber | None, EdgeIndex]] = []
|
|
136
148
|
hasNone = False
|
|
137
149
|
hasNumber = False
|
|
138
|
-
for
|
|
150
|
+
for in_edge_index in self.in_edge_indexes:
|
|
151
|
+
in_edge = self.graph.edges[in_edge_index]
|
|
139
152
|
if in_edge.kind == EdgeKinds.Control:
|
|
140
153
|
continue
|
|
141
154
|
n = in_edge.operand_number
|
|
@@ -143,14 +156,14 @@ class EdgeProxy:
|
|
|
143
156
|
hasNone = True
|
|
144
157
|
else:
|
|
145
158
|
hasNumber = True
|
|
146
|
-
operands.append((n,
|
|
159
|
+
operands.append((n, in_edge_index))
|
|
147
160
|
assert hasNone ^ hasNumber
|
|
148
161
|
if hasNumber:
|
|
149
162
|
operands.sort()
|
|
150
163
|
return [o[1] for o in operands]
|
|
151
164
|
|
|
152
165
|
|
|
153
|
-
@dataclass
|
|
166
|
+
@dataclass(slots=True)
|
|
154
167
|
class CDG:
|
|
155
168
|
"""
|
|
156
169
|
Graph of P-Code operations with Control and Data flow edges.
|
|
@@ -167,10 +180,10 @@ class CDG:
|
|
|
167
180
|
return CDG([replace(n) for n in self.nodes], [replace(e) for e in self.edges])
|
|
168
181
|
|
|
169
182
|
def compute_edge_proxy(self) -> dict[NodeIndex, EdgeProxy]:
|
|
170
|
-
cache = {index: EdgeProxy([], []) for index in range(len(self.nodes))}
|
|
171
|
-
for edge in self.edges:
|
|
172
|
-
cache[edge.source_node].
|
|
173
|
-
cache[edge.destination_node].
|
|
183
|
+
cache = {index: EdgeProxy(self, [], []) for index in range(len(self.nodes))}
|
|
184
|
+
for e, edge in enumerate(self.edges):
|
|
185
|
+
cache[edge.source_node].out_edge_indexes.append(e)
|
|
186
|
+
cache[edge.destination_node].in_edge_indexes.append(e)
|
|
174
187
|
return cache
|
|
175
188
|
|
|
176
189
|
def add_node(self, node: Node) -> NodeIndex:
|
|
@@ -29,7 +29,7 @@ class FunctionOps(ChunkOps):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def parse_binary(binary_path: Path) -> lief.Binary:
|
|
32
|
-
assert binary_path.
|
|
32
|
+
assert binary_path.is_file(), f"{binary_path} is not a file"
|
|
33
33
|
logger.debug(f"Parse {binary_path}")
|
|
34
34
|
b = lief.parse(binary_path.as_posix())
|
|
35
35
|
assert isinstance(b, lief.Binary)
|
|
@@ -26,10 +26,18 @@ class MakerFlags:
|
|
|
26
26
|
build_cfg: bool = True
|
|
27
27
|
"""Add control flow links in addition to the data flow. Defaults to True."""
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
reg_outputs_black_list: set[str] | None = None
|
|
30
30
|
"""Do not consider the given registers as graph outputs to simplify the graphs.
|
|
31
31
|
Arch-dependent register names.
|
|
32
|
-
If None, the
|
|
32
|
+
If white-list is not None, the value of black-list is ignored.
|
|
33
|
+
If both black-list and white-list are None, the content of SPECIAL_REGISTERS is used
|
|
34
|
+
for black-list.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
reg_outputs_white_list: set[str] | None = None
|
|
38
|
+
"""Consider only the given registers as graph outputs to simplify the graphs.
|
|
39
|
+
Arch-dependent register names.
|
|
40
|
+
If white-list is not None, the value of black-list is ignored.
|
|
33
41
|
"""
|
|
34
42
|
|
|
35
43
|
|
|
@@ -45,10 +53,11 @@ def make_graph(prg: RichPcodeList, flags: MakerFlags | None = None) -> CDG:
|
|
|
45
53
|
"""
|
|
46
54
|
|
|
47
55
|
if flags is None:
|
|
48
|
-
flags = MakerFlags()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
flags = MakerFlags()
|
|
57
|
+
|
|
58
|
+
black_list, white_list = flags.reg_outputs_black_list, flags.reg_outputs_white_list
|
|
59
|
+
if black_list is None and white_list is None:
|
|
60
|
+
black_list = SPECIAL_REGISTERS[prg.arch]
|
|
52
61
|
|
|
53
62
|
# Already created input nodes, by related input variable
|
|
54
63
|
input_node_indexes: dict[Var, NodeIndex] = {}
|
|
@@ -189,13 +198,22 @@ def make_graph(prg: RichPcodeList, flags: MakerFlags | None = None) -> CDG:
|
|
|
189
198
|
for var, def_op_indexes in prg.get_exit_defs().items():
|
|
190
199
|
assert def_op_indexes
|
|
191
200
|
match var.kind:
|
|
192
|
-
case VarKinds.Register:
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
201
|
+
case VarKinds.Register:
|
|
202
|
+
if white_list is not None:
|
|
203
|
+
if var.pretty_name not in white_list:
|
|
204
|
+
continue
|
|
205
|
+
else:
|
|
206
|
+
assert black_list is not None
|
|
207
|
+
if var.pretty_name.lower().startswith("tmp"):
|
|
208
|
+
# Ignore writes to temporaries
|
|
209
|
+
continue
|
|
210
|
+
if var.pretty_name in black_list:
|
|
211
|
+
# Ignore writes to special registers
|
|
212
|
+
continue
|
|
213
|
+
if var.pretty_name == "UNKNOWN":
|
|
214
|
+
# Register name not provided in P-Code
|
|
215
|
+
continue
|
|
216
|
+
assert var.pretty_name
|
|
199
217
|
output_node = Node(
|
|
200
218
|
NodeKinds.OutputRegister,
|
|
201
219
|
var.size,
|
|
@@ -309,8 +327,7 @@ def make_graph(prg: RichPcodeList, flags: MakerFlags | None = None) -> CDG:
|
|
|
309
327
|
|
|
310
328
|
graph = CDG(nodes, edges)
|
|
311
329
|
|
|
312
|
-
# Uncomment to locate a graph structure
|
|
313
|
-
# Comment to speed up
|
|
330
|
+
# Uncomment to locate a bug: graph structure vs simplifier
|
|
314
331
|
# check_graph(graph)
|
|
315
332
|
|
|
316
333
|
GraphSimplifier(graph).process()
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
|
|
1
2
|
from pcode_graph.checker import check_graph
|
|
2
|
-
from pcode_graph.graph import
|
|
3
|
+
from pcode_graph.graph import (
|
|
4
|
+
CDG,
|
|
5
|
+
EdgeIndex,
|
|
6
|
+
EdgeKinds,
|
|
7
|
+
NodeIndex,
|
|
8
|
+
OpCodes,
|
|
9
|
+
NodeKinds,
|
|
10
|
+
)
|
|
3
11
|
|
|
4
12
|
from loguru import logger
|
|
5
13
|
from pathlib import Path
|
|
@@ -13,81 +21,86 @@ class GraphSimplifier:
|
|
|
13
21
|
) -> None:
|
|
14
22
|
self.graph = graph
|
|
15
23
|
|
|
16
|
-
def _update_proxy(self):
|
|
17
24
|
self._proxy = self.graph.compute_edge_proxy()
|
|
18
25
|
|
|
26
|
+
def _finalize_removals(self):
|
|
27
|
+
|
|
28
|
+
remap: dict[int, int] = {}
|
|
29
|
+
shift = 0
|
|
30
|
+
kept_nodes = []
|
|
31
|
+
for index, node in enumerate(self.graph.nodes):
|
|
32
|
+
if node is None:
|
|
33
|
+
shift += 1
|
|
34
|
+
else:
|
|
35
|
+
remap[index] = index - shift
|
|
36
|
+
kept_nodes.append(node)
|
|
37
|
+
|
|
38
|
+
self.graph.nodes = kept_nodes
|
|
39
|
+
|
|
40
|
+
kept_edges = []
|
|
41
|
+
for edge in self.graph.edges:
|
|
42
|
+
if edge is None:
|
|
43
|
+
continue
|
|
44
|
+
edge.destination_node = remap[edge.destination_node]
|
|
45
|
+
edge.source_node = remap[edge.source_node]
|
|
46
|
+
kept_edges.append(edge)
|
|
47
|
+
|
|
48
|
+
self.graph.edges = kept_edges
|
|
49
|
+
|
|
19
50
|
def _remove_node(self, index: NodeIndex):
|
|
20
|
-
def remap_index(n: NodeIndex):
|
|
21
|
-
assert n != index
|
|
22
|
-
return n - int(n > index)
|
|
23
51
|
|
|
24
52
|
# logger.debug(f"Remove {self.graph.nodes[index]} at {index}")
|
|
25
53
|
|
|
26
|
-
# Remap edges
|
|
27
54
|
proxy = self._proxy[index]
|
|
28
55
|
|
|
29
|
-
control_succs = proxy.get_control_successors()
|
|
56
|
+
control_succs = list(proxy.get_control_successors())
|
|
30
57
|
assert (
|
|
31
58
|
len(control_succs) <= 1
|
|
32
59
|
), "More than one control successor to the node to remove"
|
|
33
60
|
|
|
34
|
-
data_preds = proxy.get_data_predecessors()
|
|
61
|
+
data_preds = list(proxy.get_data_predecessors())
|
|
35
62
|
|
|
36
63
|
# Convert to set to handle particular case of Phi node
|
|
37
64
|
assert len(data_preds) <= 1 or (
|
|
38
65
|
self.graph.nodes[index].kind == NodeKinds.Phi and len(set(data_preds)) == 1
|
|
39
66
|
), "More than one data predecessor to the node to remove"
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
):
|
|
46
|
-
# logger.debug(
|
|
47
|
-
# f"Remap {self.graph.dump_edge(Edge(kind=kind, source_node=source, destination_node=dest, operand_number=operand))}"
|
|
48
|
-
# )
|
|
49
|
-
edge = Edge(
|
|
50
|
-
source_node=remap_index(source),
|
|
51
|
-
destination_node=remap_index(dest),
|
|
52
|
-
kind=kind,
|
|
53
|
-
operand_number=operand,
|
|
54
|
-
)
|
|
55
|
-
kept_edges.append(edge)
|
|
56
|
-
|
|
57
|
-
for e in self.graph.edges:
|
|
68
|
+
for i, e in enumerate(self.graph.edges):
|
|
69
|
+
if e is None:
|
|
70
|
+
continue
|
|
71
|
+
remove = False
|
|
58
72
|
if e.source_node == index:
|
|
59
73
|
if e.kind == EdgeKinds.Data:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
self._proxy[e.source_node].out_edge_indexes.remove(i)
|
|
75
|
+
e.source_node = data_preds[0]
|
|
76
|
+
self._proxy[e.source_node].out_edge_indexes.append(i)
|
|
77
|
+
else:
|
|
78
|
+
remove = True
|
|
79
|
+
|
|
80
|
+
if e.destination_node == index:
|
|
67
81
|
if e.kind == EdgeKinds.Control:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
else:
|
|
75
|
-
remap_edge(e.kind, e.source_node, e.destination_node, e.operand_number)
|
|
82
|
+
self._proxy[e.destination_node].in_edge_indexes.remove(i)
|
|
83
|
+
e.destination_node = control_succs[0]
|
|
84
|
+
self._proxy[e.destination_node].in_edge_indexes.append(i)
|
|
85
|
+
else:
|
|
86
|
+
remove = True
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.graph.nodes.pop(index)
|
|
88
|
+
if remove:
|
|
89
|
+
self._remove_edge(i)
|
|
80
90
|
|
|
91
|
+
# Patch the graph
|
|
92
|
+
self.graph.nodes[index] = None # type: ignore
|
|
81
93
|
self._graph_changed = True
|
|
82
|
-
self._update_proxy()
|
|
83
94
|
|
|
84
95
|
# self.debug_helper()
|
|
85
96
|
|
|
86
|
-
def _remove_edge(self,
|
|
97
|
+
def _remove_edge(self, edge_index: int):
|
|
87
98
|
|
|
88
|
-
self.graph.
|
|
99
|
+
edge = self.graph.edges[edge_index]
|
|
100
|
+
self._proxy[edge.source_node].out_edge_indexes.remove(edge_index)
|
|
101
|
+
self._proxy[edge.destination_node].in_edge_indexes.remove(edge_index)
|
|
102
|
+
self.graph.edges[edge_index] = None # type: ignore
|
|
89
103
|
self._graph_changed = True
|
|
90
|
-
self._update_proxy()
|
|
91
104
|
|
|
92
105
|
def debug_helper(self):
|
|
93
106
|
try:
|
|
@@ -102,7 +115,6 @@ class GraphSimplifier:
|
|
|
102
115
|
def process(self):
|
|
103
116
|
|
|
104
117
|
self._graph_changed = True
|
|
105
|
-
self._update_proxy()
|
|
106
118
|
|
|
107
119
|
while self._graph_changed:
|
|
108
120
|
self._graph_changed = False
|
|
@@ -110,6 +122,9 @@ class GraphSimplifier:
|
|
|
110
122
|
# Remove useless phi nodes
|
|
111
123
|
for index, node in enumerate(self.graph.nodes):
|
|
112
124
|
|
|
125
|
+
if node is None:
|
|
126
|
+
continue
|
|
127
|
+
|
|
113
128
|
if node.kind == NodeKinds.Phi:
|
|
114
129
|
|
|
115
130
|
phi_data_preds = set(self._proxy[index].get_data_predecessors())
|
|
@@ -117,13 +132,12 @@ class GraphSimplifier:
|
|
|
117
132
|
# Keep this Phi node
|
|
118
133
|
continue
|
|
119
134
|
|
|
120
|
-
self._remove_node(index)
|
|
121
|
-
break
|
|
135
|
+
self._remove_node(index)
|
|
122
136
|
|
|
123
137
|
elif node.opcode == OpCodes.COPY:
|
|
124
138
|
|
|
125
139
|
ctrl_preds = set(self._proxy[index].get_control_predecessors())
|
|
126
|
-
ctrl_succs = self._proxy[index].get_control_successors()
|
|
140
|
+
ctrl_succs = set(self._proxy[index].get_control_successors())
|
|
127
141
|
|
|
128
142
|
remove = True
|
|
129
143
|
if ctrl_preds and ctrl_succs:
|
|
@@ -131,55 +145,52 @@ class GraphSimplifier:
|
|
|
131
145
|
|
|
132
146
|
remove = False
|
|
133
147
|
# Remove copies that dominates their successor...
|
|
134
|
-
if self._proxy[ctrl_succ].get_control_predecessors() == [
|
|
148
|
+
if list(self._proxy[ctrl_succ].get_control_predecessors()) == [
|
|
149
|
+
index
|
|
150
|
+
]:
|
|
135
151
|
remove = True
|
|
136
152
|
elif len(ctrl_preds) == 1:
|
|
137
153
|
# ...or are dominated by their unique predecessor
|
|
138
154
|
(ctrl_pred,) = ctrl_preds
|
|
139
|
-
if
|
|
140
|
-
|
|
141
|
-
]:
|
|
155
|
+
if list(
|
|
156
|
+
self._proxy[ctrl_pred].get_control_successors()
|
|
157
|
+
) == [index]:
|
|
142
158
|
remove = True
|
|
143
159
|
|
|
144
160
|
if remove:
|
|
145
161
|
self._remove_node(index)
|
|
146
|
-
break
|
|
147
162
|
|
|
148
|
-
elif node.kind == NodeKinds.Constant:
|
|
149
|
-
if not self._proxy[index].get_data_successors():
|
|
163
|
+
elif node.kind == NodeKinds.Constant:
|
|
164
|
+
if not list(self._proxy[index].get_data_successors()):
|
|
150
165
|
# Remove constants let orphan by other simplifications
|
|
151
166
|
self._remove_node(index)
|
|
152
|
-
break
|
|
153
167
|
|
|
154
168
|
elif node.opcode in {
|
|
155
169
|
OpCodes.INT_ZEXT,
|
|
156
170
|
OpCodes.INT_SEXT,
|
|
157
171
|
}:
|
|
158
172
|
self._remove_node(index)
|
|
159
|
-
break
|
|
160
173
|
|
|
161
174
|
elif node.opcode == OpCodes.SUBPIECE:
|
|
162
175
|
|
|
163
|
-
operands: list[
|
|
164
|
-
|
|
176
|
+
operands: list[EdgeIndex] = self._proxy[index].get_input_edges()
|
|
165
177
|
assert len(operands) == 2
|
|
166
|
-
|
|
178
|
+
source_node_index = self.graph.edges[operands[1]].source_node
|
|
179
|
+
num_trunc_bytes = self.graph.nodes[source_node_index]
|
|
167
180
|
assert num_trunc_bytes.kind == NodeKinds.Constant
|
|
168
181
|
if num_trunc_bytes.value == 0:
|
|
169
182
|
self._remove_edge(operands[1])
|
|
170
|
-
self._remove_node(index)
|
|
171
|
-
break
|
|
183
|
+
self._remove_node(index)
|
|
172
184
|
|
|
173
185
|
elif node.opcode == OpCodes.INT_MULT:
|
|
174
186
|
# Remove mulitiplication by one
|
|
175
|
-
|
|
176
|
-
|
|
187
|
+
for op_edge_index in self._proxy[index].get_input_edges():
|
|
188
|
+
op_edge = self.graph.edges[op_edge_index]
|
|
177
189
|
o = op_edge.source_node
|
|
178
190
|
op = self.graph.nodes[o]
|
|
179
191
|
if op.kind == NodeKinds.Constant and op.value == 1:
|
|
180
|
-
self._remove_edge(
|
|
181
|
-
self._remove_node(index)
|
|
192
|
+
self._remove_edge(op_edge_index)
|
|
193
|
+
self._remove_node(index)
|
|
182
194
|
break
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
break
|
|
195
|
+
|
|
196
|
+
self._finalize_removals()
|
|
@@ -98,7 +98,7 @@ ret
|
|
|
98
98
|
""",
|
|
99
99
|
x86_64,
|
|
100
100
|
outdir,
|
|
101
|
-
flags=MakerFlags(
|
|
101
|
+
flags=MakerFlags(reg_outputs_black_list=special_regs),
|
|
102
102
|
)
|
|
103
103
|
|
|
104
104
|
check_inputs(g, "#0x8", "RSP", "MEMORY")
|
|
@@ -131,7 +131,7 @@ ldp x29, x30, [sp], #0x10
|
|
|
131
131
|
""",
|
|
132
132
|
arm64,
|
|
133
133
|
outdir,
|
|
134
|
-
flags=MakerFlags(
|
|
134
|
+
flags=MakerFlags(reg_outputs_black_list=special_regs),
|
|
135
135
|
)
|
|
136
136
|
|
|
137
137
|
load1, load2 = [n for n, node in enumerate(g.nodes) if node.opcode == OpCodes.LOAD]
|
|
@@ -166,10 +166,10 @@ ldr x2, [sp, 0x10]
|
|
|
166
166
|
|
|
167
167
|
(load_index,) = [n for n, node in enumerate(g.nodes) if node.opcode == OpCodes.LOAD]
|
|
168
168
|
proxy = g.compute_edge_proxy()
|
|
169
|
-
phi_index, add_index = proxy[load_index].get_data_predecessors()
|
|
169
|
+
phi_index, add_index = list(proxy[load_index].get_data_predecessors())
|
|
170
170
|
assert g.nodes[phi_index].kind == NodeKinds.Phi
|
|
171
171
|
assert g.nodes[add_index].opcode == OpCodes.INT_ADD
|
|
172
|
-
assert len(proxy[phi_index].get_data_predecessors()) == 3
|
|
172
|
+
assert len(list(proxy[phi_index].get_data_predecessors())) == 3
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def test_call_ind_arm(outdir: Path, arm64: ArchContext, log):
|
|
@@ -199,7 +199,7 @@ mul x1, x0, x0
|
|
|
199
199
|
else:
|
|
200
200
|
assert False, "Call not found"
|
|
201
201
|
|
|
202
|
-
assert len(g.compute_edge_proxy()[call].get_control_successors()) == 2
|
|
202
|
+
assert len(list(g.compute_edge_proxy()[call].get_control_successors())) == 2
|
|
203
203
|
|
|
204
204
|
|
|
205
205
|
def test_div(outdir: Path, x86_64: ArchContext, log):
|
|
@@ -240,7 +240,7 @@ mov x0, x6
|
|
|
240
240
|
|
|
241
241
|
proxy = g.compute_edge_proxy()
|
|
242
242
|
|
|
243
|
-
assert proxy[x0_output].get_data_predecessors() == [x1_input]
|
|
243
|
+
assert list(proxy[x0_output].get_data_predecessors()) == [x1_input]
|
|
244
244
|
|
|
245
245
|
|
|
246
246
|
def test_various_opcodes(outdir: Path, arm64: ArchContext, log):
|
|
@@ -409,14 +409,14 @@ ret
|
|
|
409
409
|
|
|
410
410
|
proxy = g.compute_edge_proxy()
|
|
411
411
|
(load,) = [i for i, n in enumerate(g.nodes) if n.opcode == OpCodes.LOAD]
|
|
412
|
-
assert len(proxy[load].get_control_predecessors()) == 2
|
|
412
|
+
assert len(list(proxy[load].get_control_predecessors())) == 2
|
|
413
413
|
|
|
414
414
|
(rax,) = [
|
|
415
415
|
i
|
|
416
416
|
for i, n in enumerate(g.nodes)
|
|
417
417
|
if n.kind == NodeKinds.OutputRegister and n.register_name == "RAX"
|
|
418
418
|
]
|
|
419
|
-
(rax_pred,) = proxy[rax].get_data_predecessors()
|
|
419
|
+
(rax_pred,) = list(proxy[rax].get_data_predecessors())
|
|
420
420
|
assert g.nodes[rax_pred].kind == NodeKinds.Phi
|
|
421
421
|
|
|
422
422
|
|
|
@@ -450,7 +450,7 @@ jmp rax
|
|
|
450
450
|
|
|
451
451
|
(end,) = [i for i, n in enumerate(g.nodes) if n.kind == NodeKinds.External]
|
|
452
452
|
(branchind,) = [i for i, n in enumerate(g.nodes) if n.opcode == OpCodes.BRANCHIND]
|
|
453
|
-
assert g.compute_edge_proxy()[end].get_control_predecessors() == [branchind]
|
|
453
|
+
assert list(g.compute_edge_proxy()[end].get_control_predecessors()) == [branchind]
|
|
454
454
|
|
|
455
455
|
|
|
456
456
|
def test_imul(outdir: Path, x86_64: ArchContext):
|
|
@@ -569,7 +569,7 @@ call 0x546fb
|
|
|
569
569
|
asm,
|
|
570
570
|
x86_64,
|
|
571
571
|
outdir / "full",
|
|
572
|
-
flags=MakerFlags(
|
|
572
|
+
flags=MakerFlags(reg_outputs_black_list=ignored_regs),
|
|
573
573
|
)
|
|
574
574
|
check_inputs(g2, "RSP", "#0x8", "#0x5") # 0x5 is return address
|
|
575
575
|
check_outputs(g2, "RSP", "MEMORY")
|
|
@@ -1131,7 +1131,7 @@ RET
|
|
|
1131
1131
|
|
|
1132
1132
|
for n, node in enumerate(g.nodes):
|
|
1133
1133
|
if node.kind == NodeKinds.Constant:
|
|
1134
|
-
assert proxy[n].get_data_successors(), f"Orphan constant {node}"
|
|
1134
|
+
assert list(proxy[n].get_data_successors()), f"Orphan constant {node}"
|
|
1135
1135
|
|
|
1136
1136
|
|
|
1137
1137
|
def test_lea_simplification(outdir: Path, x86_64: ArchContext, log):
|
|
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
|
|
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
|