pcode-graph 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,299 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Any, Iterator
4
+ from loguru import logger
5
+ from pypcode import OpCode
6
+ from pcode_graph.arch import Arch
7
+ import lief
8
+ from pcode_graph.translator import ChunkOps, Translator
9
+
10
+ lief.disable_leak_warning()
11
+
12
+
13
+ @dataclass
14
+ class ChunkCode:
15
+ address: int
16
+ content: bytes
17
+
18
+
19
+ @dataclass
20
+ class FunctionCode:
21
+ name: str
22
+ address: int
23
+ content: bytes
24
+
25
+
26
+ @dataclass
27
+ class FunctionOps(ChunkOps):
28
+ function_name: str
29
+
30
+
31
+ def parse_binary(binary_path: Path) -> lief.Binary:
32
+ assert binary_path.exists(), f"{binary_path} not found"
33
+ logger.debug(f"Parse {binary_path}")
34
+ b = lief.parse(binary_path.as_posix())
35
+ assert isinstance(b, lief.Binary)
36
+ return b
37
+
38
+
39
+ def get_architecture(binary: Path | lief.Binary) -> Arch:
40
+ """Guesses the architecture of given binary."""
41
+
42
+ if isinstance(binary, Path):
43
+ binary = parse_binary(binary)
44
+
45
+ # ELF
46
+ if isinstance(binary, lief.ELF.Binary):
47
+
48
+ match binary.header.machine_type:
49
+ case lief.ELF.ARCH.ARM:
50
+ arch = Arch.arm_32
51
+ case lief.ELF.ARCH.AARCH64:
52
+ arch = Arch.arm_64
53
+ case lief.ELF.ARCH.X86_64:
54
+ arch = Arch.x86_64
55
+ case lief.ELF.ARCH.I386:
56
+ arch = Arch.x86_32
57
+ case lief.ELF.ARCH.MIPS:
58
+ if binary.header.identity_data != lief.ELF.Header.ELF_DATA.MSB:
59
+ raise ValueError("Unsupported edianness")
60
+ if binary.header.identity_class == lief.ELF.Header.CLASS.ELF64:
61
+ arch = Arch.mips_64
62
+ else:
63
+ flags = set(binary.header.flags_list)
64
+ _P = lief.ELF.PROCESSOR_FLAGS
65
+ if _P.MIPS_ABI2 in flags or bool(
66
+ flags
67
+ & {
68
+ _P.MIPS_ARCH_3, _P.MIPS_ARCH_4, _P.MIPS_ARCH_5, _P.MIPS_ARCH_64, _P.MIPS_ARCH_64R2, _P.MIPS_ARCH_64R6, # type: ignore # fmt: skip
69
+ }
70
+ ):
71
+ arch = Arch.mips_64
72
+ else:
73
+ arch = Arch.mips_32
74
+ case _ as v:
75
+ raise ValueError(f"Unsupported CPU type {v}")
76
+
77
+ # Mach-O
78
+ elif isinstance(binary, lief.MachO.FatBinary):
79
+ arch = None
80
+ for sub_binary in binary:
81
+ match sub_binary.header.cpu_type:
82
+ case lief.MachO.Header.CPU_TYPE.ARM64:
83
+ arch = Arch.arm_64
84
+ case lief.MachO.Header.CPU_TYPE.X86_64:
85
+ arch = Arch.x86_64
86
+ case _:
87
+ continue
88
+ break
89
+ else:
90
+ raise ValueError(f"No sub binary found with supported architecture")
91
+
92
+ elif isinstance(binary, lief.MachO.Binary):
93
+ match binary.header.cpu_type:
94
+ case lief.MachO.Header.CPU_TYPE.ARM64:
95
+ arch = Arch.arm_64
96
+ case lief.MachO.Header.CPU_TYPE.X86_64:
97
+ arch = Arch.x86_64
98
+ case _ as v:
99
+ raise ValueError(f"Unsupported CPU type {v}")
100
+
101
+ # PE
102
+ elif isinstance(binary, lief.PE.Binary):
103
+ match binary.header.machine:
104
+ case lief.PE.Header.MACHINE_TYPES.ARM64:
105
+ arch = Arch.arm_64
106
+ case lief.PE.Header.MACHINE_TYPES.AMD64:
107
+ arch = Arch.x86_64
108
+ case _ as v:
109
+ raise ValueError(f"Unsupported CPU type {v}")
110
+
111
+ else:
112
+ raise ValueError("Unsupported binary format")
113
+
114
+ return arch
115
+
116
+
117
+ def iter_code_sections(binary: Path | lief.Binary) -> Iterator[ChunkCode]:
118
+
119
+ if isinstance(binary, Path):
120
+ binary = parse_binary(binary)
121
+
122
+ if binary.format == lief.Binary.FORMATS.MACHO:
123
+ flags = {
124
+ lief.MachO.Section.FLAGS.PURE_INSTRUCTIONS, # type: ignore
125
+ lief.MachO.Section.FLAGS.SOME_INSTRUCTIONS, # type: ignore
126
+ }
127
+ for section in binary.sections:
128
+ if flags.intersection(section.flags_list): # type: ignore
129
+ yield ChunkCode(section.virtual_address, section.content.tobytes())
130
+
131
+ elif binary.format == lief.Binary.FORMATS.PE:
132
+ for section in binary.sections:
133
+ if section.has_characteristic( # type: ignore
134
+ lief.PE.Section.CHARACTERISTICS.CNT_CODE
135
+ ) and section.has_characteristic( # type: ignore
136
+ lief.PE.Section.CHARACTERISTICS.MEM_EXECUTE
137
+ ):
138
+ yield ChunkCode(section.virtual_address, section.content.tobytes())
139
+
140
+ elif binary.format == lief.Binary.FORMATS.ELF:
141
+ for section in binary.sections:
142
+ if section.has(lief.ELF.Section.FLAGS.EXECINSTR): # type: ignore
143
+ yield ChunkCode(section.virtual_address, section.content.tobytes())
144
+
145
+
146
+ def get_function(binary: Path | lief.Binary, name: str) -> FunctionCode:
147
+
148
+ if isinstance(binary, Path):
149
+ binary = parse_binary(binary)
150
+
151
+ for f in iter_functions(binary):
152
+ if f.name == name:
153
+ return f
154
+
155
+ raise LookupError(f"Function {name} not found in {binary}")
156
+
157
+
158
+ def iter_functions(binary: Path | lief.Binary) -> Iterator[FunctionCode]:
159
+
160
+ if isinstance(binary, Path):
161
+ binary = parse_binary(binary)
162
+
163
+ cache: dict[Any, tuple[bytes, int]] = {}
164
+ sym: lief.ELF.Symbol
165
+
166
+ for sym in binary.symtab_symbols: # type: ignore
167
+ if sym.type != lief.ELF.Symbol.TYPE.FUNC or sym.size == 0:
168
+ continue
169
+ section: lief.ELF.Section | None = sym.section
170
+ if section is None:
171
+ continue
172
+
173
+ section_id = section.name
174
+ if section_id not in cache:
175
+ cache[section_id] = (bytes(section.content), section.virtual_address)
176
+
177
+ section_content, section_address = cache[section_id]
178
+
179
+ assert isinstance(sym.name, str)
180
+ yield FunctionCode(
181
+ sym.name,
182
+ section_address + sym.value,
183
+ section_content[sym.value : sym.value + sym.size],
184
+ )
185
+
186
+
187
+ def lookup_chunk(
188
+ binary: Path | lief.Binary,
189
+ start_address: int,
190
+ end_address: int,
191
+ ) -> bytes:
192
+ """Returns the code at given address from the given binary.
193
+
194
+ Args:
195
+ binary (Path | lief.Binary): Binary to read.
196
+ start_address (int): Address of the first instruction to return.
197
+ end_address (int): Address of the first instruction after the returned chunk.
198
+
199
+ Returns:
200
+ ChunkCode: A piece of binary code.
201
+ """
202
+
203
+ for section in iter_code_sections(binary):
204
+ if (
205
+ section.address <= start_address
206
+ and section.address + len(section.content) >= end_address
207
+ ):
208
+ offset = start_address - section.address
209
+ size = end_address - start_address
210
+ return section.content[offset : offset + size]
211
+
212
+ raise LookupError("Cannot find a section containing given range")
213
+
214
+
215
+ def collect_functions(
216
+ binary: Path | lief.Binary,
217
+ translator: Translator,
218
+ ) -> Iterator[FunctionOps]:
219
+
220
+ logger.debug(f"Extract functions")
221
+
222
+ for function in iter_functions(binary):
223
+
224
+ instructions = list(translator.disassemble(function.content, function.address))
225
+ operations = translator.translate(function.content, function.address)
226
+ yield FunctionOps(
227
+ len(instructions),
228
+ function.address,
229
+ operations,
230
+ function.name,
231
+ )
232
+
233
+
234
+ def collect_basic_blocks(
235
+ binary: Path | lief.Binary,
236
+ translator: Translator,
237
+ min_instructions: int,
238
+ max_instructions: int,
239
+ ) -> Iterator[ChunkOps]:
240
+ """Extracts the P-Code operations of basic blocks found in a binary, using LIEF library."""
241
+
242
+ logger.debug(f"Extract basic blocks")
243
+
244
+ # Extract P-Code operations of executable sections
245
+ for section in iter_code_sections(binary):
246
+
247
+ # Slice into basic blocks
248
+ num_instructions = 0
249
+ operations = []
250
+ bb_start_offset = 0
251
+
252
+ # First disassemble to translate into P-Code one instruction at a time,
253
+ # in order to avoid consuming too much RAM.
254
+ for instr in translator.disassemble(section.content, section.address):
255
+
256
+ instr_start_offset = instr.address - section.address
257
+ instr_end_offset = instr_start_offset + instr.length
258
+ instr_code = section.content[instr_start_offset:instr_end_offset]
259
+
260
+ instr_ops = translator.translate(instr_code, instr.address)
261
+
262
+ num_instructions += 1
263
+
264
+ if num_instructions <= max_instructions:
265
+ operations += instr_ops
266
+
267
+ last_op = instr_ops[-1]
268
+ if last_op.opcode not in {
269
+ OpCode.BRANCH,
270
+ OpCode.BRANCHIND,
271
+ OpCode.CBRANCH,
272
+ OpCode.RETURN,
273
+ }:
274
+ # Not at the end of a basic block
275
+ continue
276
+
277
+ if min_instructions <= num_instructions <= max_instructions:
278
+ yield ChunkOps(
279
+ num_instructions,
280
+ section.address + bb_start_offset,
281
+ # section.content[bb_start_offset:instr_end_offset],
282
+ operations,
283
+ )
284
+
285
+ bb_start_offset = instr_end_offset
286
+ num_instructions = 0
287
+ operations = []
288
+
289
+
290
+ def collect_chunks(
291
+ binary: Path | lief.Binary,
292
+ translator: Translator,
293
+
294
+ ) -> Iterator[ChunkOps]:
295
+ """Extracts the P-Code operations of the sections found in a binary, using LIEF library."""
296
+
297
+ for section in iter_code_sections(binary):
298
+ yield from translator.iter_operations(section.content, section.address)
299
+
pcode_graph/log.py ADDED
@@ -0,0 +1,36 @@
1
+ from pathlib import Path
2
+ import sys
3
+ from loguru import logger
4
+
5
+
6
+ def setup_logger(
7
+ log_path: Path | None = None,
8
+ stderr_verbosity="INFO",
9
+ file_verbosity="DEBUG",
10
+ ) -> int | None:
11
+ """Setups the global logger with two sinks: a file and stderr.
12
+ Returns a handle to the file sink.
13
+ """
14
+
15
+ logger.remove()
16
+ logger.add(
17
+ sys.stderr,
18
+ level=stderr_verbosity,
19
+ format="<level>{message}</level>",
20
+ backtrace=False,
21
+ diagnose=False,
22
+ )
23
+
24
+ if log_path is not None:
25
+ logger.info(f"Logging in {log_path}")
26
+ log_path.parent.mkdir(exist_ok=True, parents=True)
27
+ if log_path.exists():
28
+ logger.warning(f"Overwriting logfile: {log_path}")
29
+ log_path.unlink()
30
+ return logger.add(
31
+ log_path,
32
+ level=file_verbosity,
33
+ format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
34
+ "<level>{level: <8}</level> | "
35
+ "<cyan>{name}:{line}: in {function}:</cyan> <level>{message}</level>",
36
+ )
pcode_graph/maker.py ADDED
@@ -0,0 +1,369 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from pcode_graph.analysis import (
4
+ EntryIndex,
5
+ OpIndex,
6
+ RichPcodeList,
7
+ UnknownCodeIndex,
8
+ ExitIndex,
9
+ Var,
10
+ VarKinds,
11
+ )
12
+ from pcode_graph.asm import Assembler
13
+ from pcode_graph.checker import check_graph
14
+ from pcode_graph.graph import CDG, Edge, EdgeKinds, NodeIndex, NodeKinds, Node
15
+ from pcode_graph.pcode import from_native_opcode
16
+ from pcode_graph.registers import SPECIAL_REGISTERS
17
+ from pcode_graph.simplifier import GraphSimplifier
18
+ from pcode_graph.translator import Translator
19
+
20
+ from pypcode import OpCode
21
+ from loguru import logger
22
+
23
+
24
+ @dataclass
25
+ class MakerFlags:
26
+ build_cfg: bool = True
27
+ """Add control flow links in addition to the data flow. Defaults to True."""
28
+
29
+ ignore_reg_outputs: set[str] | None = None
30
+ """Do not consider the given registers as graph outputs to simplify the graphs.
31
+ Arch-dependent register names.
32
+ If None, the result of pcode.get_special_register_names() is used.
33
+ """
34
+
35
+
36
+ def make_graph(prg: RichPcodeList, flags: MakerFlags | None = None) -> CDG:
37
+ """Makes a graph from a list of P-Code operations.
38
+
39
+ Args:
40
+ prg (RichPcodeList): the input list of operations, enriched by analyses.
41
+ flags (MakerFlags): determines what to put in the graph.
42
+
43
+ Returns:
44
+ CDG: the graph built.
45
+ """
46
+
47
+ if flags is None:
48
+ flags = MakerFlags()
49
+ ignore_reg_outputs = flags.ignore_reg_outputs
50
+ if ignore_reg_outputs is None:
51
+ ignore_reg_outputs = SPECIAL_REGISTERS[prg.arch]
52
+
53
+ # Already created input nodes, by related input variable
54
+ input_node_indexes: dict[Var, NodeIndex] = {}
55
+
56
+ # Already created operation nodes, by related P-Code operation
57
+ operation_node_indexes: dict[OpIndex, NodeIndex] = {}
58
+
59
+ # Already created phi nodes, by (sorted) possible alternatives
60
+ # to avoid duplicate phi nodes
61
+ phi_node_indexes: dict[tuple[NodeIndex, NodeIndex], NodeIndex] = {}
62
+
63
+ # Created nodes of all kinds
64
+ nodes: list[Node] = []
65
+
66
+ # Created edges
67
+ edges: list[Edge] = []
68
+
69
+ # To build the data graph recursively
70
+ newly_taken_ops: list[tuple[OpIndex, NodeIndex]] = []
71
+
72
+ # External code node if created
73
+ external_node_index: NodeIndex | None = None
74
+
75
+ # End node if created
76
+ end_node_index: NodeIndex | None = None
77
+
78
+ # begin node has zero index
79
+ if flags.build_cfg:
80
+ nodes.append(Node(NodeKinds.Begin))
81
+ begin_index = 0
82
+ else:
83
+ begin_index = -1
84
+
85
+ def get_or_create_terminal(op_index: OpIndex) -> NodeIndex:
86
+ nonlocal external_node_index, end_node_index
87
+
88
+ if op_index == ExitIndex:
89
+ if end_node_index is None:
90
+ end_node_index = len(nodes)
91
+ nodes.append(Node(NodeKinds.End))
92
+ return end_node_index
93
+ assert op_index == UnknownCodeIndex
94
+ if external_node_index is None:
95
+ external_node_index = len(nodes)
96
+ nodes.append(Node(NodeKinds.External))
97
+ return external_node_index
98
+
99
+ def get_or_create_operation(op_index: OpIndex) -> NodeIndex:
100
+ node_index = operation_node_indexes.get(op_index)
101
+ if node_index is None:
102
+ op = prg.operations[op_index]
103
+ node_index = len(nodes)
104
+ newly_taken_ops.append((op_index, node_index))
105
+ nodes.append(
106
+ Node(
107
+ NodeKinds.Operation,
108
+ op.output.size if op.output is not None else 0,
109
+ opcode=from_native_opcode(op.opcode),
110
+ )
111
+ )
112
+ operation_node_indexes[op_index] = node_index
113
+
114
+ # logger.debug(f"Create node {node_index}: {nodes[-1]} from operation {op_index}: {dump_operation(op)}")
115
+ return node_index
116
+
117
+ def get_or_create_input(var: Var) -> NodeIndex:
118
+ node_index = input_node_indexes.get(var)
119
+ if node_index is None:
120
+ match var.kind:
121
+ case VarKinds.Constant:
122
+ node = Node(NodeKinds.Constant, var.size, var.value)
123
+ case VarKinds.Register:
124
+ node = Node(
125
+ NodeKinds.InputRegister,
126
+ var.size,
127
+ register_id=var.value,
128
+ register_name=var.pretty_name,
129
+ )
130
+ case VarKinds.Ram:
131
+ node = Node(NodeKinds.ReadMemory, var.size)
132
+ case VarKinds.IndirectAccess:
133
+ node = Node(NodeKinds.ReadMemory, var.size)
134
+ case _:
135
+ raise ValueError(f"Unexpected kind for input {var.kind}")
136
+ node_index = len(nodes)
137
+ nodes.append(node)
138
+ input_node_indexes[var] = node_index
139
+ return node_index
140
+
141
+ def add_control_edge(
142
+ from_node: NodeIndex, to_node: NodeIndex, operand_number: int | None
143
+ ):
144
+ edges.append(Edge(from_node, to_node, EdgeKinds.Control, operand_number))
145
+
146
+ def add_data_edge(
147
+ from_node: NodeIndex, to_node: NodeIndex, operand_number: int | None
148
+ ):
149
+ edges.append(Edge(from_node, to_node, EdgeKinds.Data, operand_number))
150
+
151
+ def link_defs(
152
+ var: Var,
153
+ def_op_indexes: set[OpIndex],
154
+ user_node_index: NodeIndex,
155
+ operand_number: int | None,
156
+ ):
157
+
158
+ # Collect data preds to see if we need a phi node
159
+ # Sort defs to remain determinist
160
+ pred_node_indexes = []
161
+ for def_op_index in sorted(list(def_op_indexes)):
162
+ if def_op_index == EntryIndex:
163
+ # Input variable
164
+ pred_node_indexes.append(get_or_create_input(var))
165
+ else:
166
+ pred_node_indexes.append(get_or_create_operation(def_op_index))
167
+
168
+ if len(pred_node_indexes) == 1:
169
+ # Simple def, direct link
170
+ add_data_edge(pred_node_indexes[0], user_node_index, operand_number)
171
+ else:
172
+ # Create phi node
173
+ assert pred_node_indexes
174
+ pred_node_indexes.sort()
175
+ key = tuple(pred_node_indexes)
176
+ phi_node_index = phi_node_indexes.get(key)
177
+ if phi_node_index is None:
178
+ phi_node = Node(NodeKinds.Phi, var.size)
179
+ phi_node_index = len(nodes)
180
+ nodes.append(phi_node)
181
+ phi_node_indexes[key] = phi_node_index
182
+ for pred_node_index in pred_node_indexes:
183
+ add_data_edge(pred_node_index, phi_node_index, None)
184
+ add_data_edge(phi_node_index, user_node_index, operand_number)
185
+
186
+ # Build the data flow
187
+
188
+ # Start from writes to outputs registers and memory
189
+ for var, def_op_indexes in prg.get_exit_defs().items():
190
+ assert def_op_indexes
191
+ match var.kind:
192
+ case VarKinds.Register:
193
+ if var.pretty_name.lower().startswith("tmp"):
194
+ # Ignore writes to temporaries
195
+ continue
196
+ if var.pretty_name in ignore_reg_outputs:
197
+ # Ignore writes to special registers
198
+ continue
199
+ output_node = Node(
200
+ NodeKinds.OutputRegister,
201
+ var.size,
202
+ register_id=var.value,
203
+ register_name=var.pretty_name,
204
+ )
205
+
206
+ case VarKinds.IndirectAccess | VarKinds.Ram:
207
+ output_node = Node(NodeKinds.WrittenMemory, var.size)
208
+
209
+ case VarKinds.Unique:
210
+ # Ignore writes to temporaries
211
+ continue
212
+
213
+ case VarKinds.Constant:
214
+ raise ValueError(f"Unexpected output kind {var.kind}")
215
+
216
+ output_node_index = len(nodes)
217
+ nodes.append(output_node)
218
+ # logger.debug(f"Start from output {output_node_index} {output_node}")
219
+ link_defs(var, def_op_indexes, output_node_index, None)
220
+
221
+ if flags.build_cfg:
222
+ # Also add conditional branching or branching to unknown address
223
+ for op_index, op in enumerate(prg.operations):
224
+ if not prg.reachable[op_index]:
225
+ continue
226
+ if op.opcode == OpCode.CBRANCH or (
227
+ UnknownCodeIndex in prg.successors.get(op_index, set())
228
+ and op.opcode != OpCode.IMARK
229
+ ):
230
+ get_or_create_operation(op_index)
231
+
232
+ # Follow predecessors recursively until the whole dataflow graph is built
233
+ while newly_taken_ops:
234
+ op_index, node_index = newly_taken_ops.pop()
235
+
236
+ for number, (var, defs) in enumerate(prg.iter_input_defs(op_index)):
237
+
238
+ if not defs:
239
+ # No definition means this is a constant
240
+ pred_node_index = get_or_create_input(var)
241
+ add_data_edge(pred_node_index, node_index, number)
242
+ continue
243
+
244
+ link_defs(var, defs, node_index, number)
245
+
246
+ if flags.build_cfg:
247
+ # Build the control flow graph, jumping over nodes which
248
+ # do not already belong to the data flow graph.
249
+
250
+ for op_index, node_index in operation_node_indexes.items():
251
+
252
+ if not prg.reachable[op_index]:
253
+ continue
254
+
255
+ successors = prg.successors.get(op_index, [])
256
+
257
+ for number, successor_op_index in enumerate(successors):
258
+ if successor_op_index < 0: # ie. in {UnknownCodeIndex, ExitIndex}
259
+ add_control_edge(
260
+ node_index, get_or_create_terminal(successor_op_index), number
261
+ )
262
+ continue
263
+
264
+ successor_node_index = operation_node_indexes.get(successor_op_index)
265
+ seen_ops: set[OpIndex] = set()
266
+
267
+ while successor_node_index is None:
268
+
269
+ # This successor is not part of the dataflow graph, skip it.
270
+
271
+ # Having more than one successor would imply being a CBRANCH,
272
+ # but CBRANCH are always put in the dataflow, see above.
273
+ (successor_op_index,) = prg.successors[successor_op_index]
274
+
275
+ if successor_op_index in seen_ops:
276
+ logger.warning("Looping CFG without side effects")
277
+ add_control_edge(node_index, node_index, number)
278
+ break
279
+
280
+ seen_ops.add(successor_op_index)
281
+
282
+ if successor_op_index < 0: # ie. in {UnknownCodeIndex, ExitIndex}
283
+ successor_node_index = get_or_create_terminal(
284
+ successor_op_index
285
+ )
286
+ continue
287
+
288
+ successor_node_index = operation_node_indexes.get(
289
+ successor_op_index
290
+ )
291
+
292
+ else:
293
+ assert successor_node_index >= 0
294
+ add_control_edge(node_index, successor_node_index, number)
295
+
296
+ if len(nodes) == 1:
297
+ # Empty graph
298
+ add_control_edge(begin_index, get_or_create_terminal(ExitIndex), None)
299
+ else:
300
+ # Link the entry node to the first taken operation
301
+ # TODO: specify entry operation
302
+ entry_op_index = 0
303
+ while entry_op_index not in operation_node_indexes:
304
+ successor_op_indexes = tuple(prg.successors.get(entry_op_index, ()))
305
+ assert len(successor_op_indexes) == 1
306
+ # Advance to next operation
307
+ entry_op_index = successor_op_indexes[0]
308
+ add_control_edge(begin_index, operation_node_indexes[entry_op_index], None)
309
+
310
+ graph = CDG(nodes, edges)
311
+
312
+ # Uncomment to locate a graph structure bug
313
+ # Comment to speed up
314
+ # check_graph(graph)
315
+
316
+ GraphSimplifier(graph).process()
317
+ check_graph(graph)
318
+
319
+ return graph
320
+
321
+
322
+ def make_graph_from_binary(
323
+ translator: Translator,
324
+ code: bytes,
325
+ base_address: int,
326
+ flags: MakerFlags | None = None,
327
+ ) -> CDG:
328
+ """Makes a CDG graph from a piece of binary code.
329
+
330
+ Args:
331
+ translator (Translator): The P-Code translator context.
332
+ code (bytes): The binary code to translate.
333
+ base_address (int): The address of the first instruction of the given code.
334
+ flags (MakerFlags): determines what to put in the graph.
335
+
336
+ Returns:
337
+ CDG: the graph built.
338
+ """
339
+
340
+ operations = translator.translate(code, base_address)
341
+ prg = RichPcodeList(translator.arch, operations=operations)
342
+ return make_graph(prg, flags)
343
+
344
+
345
+ def make_graph_from_asm(
346
+ assembler: Assembler,
347
+ translator: Translator,
348
+ asm: str,
349
+ base_address: int = 0,
350
+ flags: MakerFlags | None = None,
351
+ ) -> CDG:
352
+ """Makes a CDG graph from a piece of binary code. Mainly for testing purpose.
353
+
354
+ Args:
355
+ translator (Translator): The P-Code translator context.
356
+ asm (str): The assembly code to translate.
357
+ base_address (int): The address of the first instruction of the given code.
358
+ flags (MakerFlags): determines what to put in the graph.
359
+
360
+ Returns:
361
+ CDG: the graph built.
362
+ """
363
+
364
+ return make_graph_from_binary(
365
+ code=assembler.assemble(asm, base_address),
366
+ base_address=base_address,
367
+ translator=translator,
368
+ flags=flags,
369
+ )