Skip to main content

P-Code Graph

This library helps to build, display and vectorize the Control-Data Graph (CDG) of a chunk of binary code, using Ghidra P-Code representation.

It ams at producing compact graphs, with a diameter as low as possible, but without losing the accurate semantics of the code.

Analyses remain low-level: no assumption is done on the used ABI in order to support exotic code for various task:

  • semantic-based similarity research
  • static code deobfuscation
  • ROP gadget chaining

Command-line interface

A cdg tool is provided to play with binary of assembly files.

For example to dump the CDG of a function in markdown format:

(venv) $ cdg mermaid test.o -f func -o graph.md

Or to display the graph of the whole content of an assembly file in HTML:

(venv) $ cdg html test.asm -o graph.html

Tests

Run tests

$ python3.14 -m venv venv
$ source venv/bin/activate
(venv) $ pip install .[test,torch]
(venv) $ python -m pytest tests

Test output

Tests using the outdir fixture generate their files in a dedicated output directory, in which you can usually find:

  • the log file
  • the dump of P-Code operations
  • the dump of generated graph in markdown+mermaid format
  • a HTML file to display the related graph in a browser

You can display the dataflow graph resulting from a test in its output directory:

$ firefox out/tests/maker_test/test_call/graph.html

API

Graph representation

The graph.py module contains the data classes defining a CDG.

classDiagram
  Graph *-- Node : nodes
  Graph *-- Edge : edges
  Graph: +list[Node] nodes
  Graph: +list[Edge] edges

  Node: +NodeKinds kind
  Node: +int size
  Node: +int value
  Node: +int register_id
  Node: +str register_name
  Node: +OpCodes opcode

  Edge: +EdgeKinds kind
  Edge: +int source_node
  Edge: +int destination_node
  Edge: +Optional[int] operand_number

Build a graph

We use LIEF library as a default binary loader. To build a graph from binary code:

from pcode_graph.maker import make_graph_from_binary
from pcode_graph.pcode import Translator
from pcode_graph.arch import Arch
from pcode_graph.lief_importer import iter_functions

translator = Translator(Arch.x86_64)

for function in iter_functions(Path("a.out")):
    graph = make_graph_from_binary(translator, 
                                   function.content, 
                                   function.address,
                                   ignore_flag_outputs=True)

Note that you can alternatively collect basic blocks (see lief_importer.py module), or use your own loader and make a graph from an arbitrary piece of binary code.

For testing purpose, you can also build it from assembly:

from pcode_graph.maker import make_graph_from_asm
from pcode_graph.asm import Assembler
from pcode_graph.pcode import Translator
from pcode_graph.arch import Arch

translator = Translator(Arch.arm_64)
assembler = Assembler(Arch.arm_64)
asm = """
add x0, x1, x2
blr x16
mul x1, x0, x0
"""

graph = make_graph_from_asm(assembler, translator, asm)

Pointer alias analysis

Addresses are resolved relatively to the stack pointer at entry, or as absolute constants, to link each LOAD to the values written by the STOREs. Stack and absolute addresses are supposed not to alias each other. Apart from this, no advanced alias analysis is performed and no assumption is made about the used ABI: calls to unknown code and stores to unresolved addresses may write anywhere.

View the graph

You can unparse the graph into markdown using the mermaid syntax:

with open("graph.md", "w") as f:
    f.write(str(graph))

The module pcode_graph.view wraps the pyvis library to display graphs in a browser:

from pcode_graph.view import draw_graph

draw_graph(graph, "out/graph.html", open_browser=True)

Vectorize the graph

To convert a graph to tensors, you will need the list of the registers you expect your model to know, in order to convert the references to these registers into tensors.

If you know in advance a list of interesting registers, the map_registers function will create a dict that maps these registers and their alias to an hot-encoded vector:

from pcode_graph.gnn_exporter import map_registers

reg_map = map_registers("rsp", "rax", "rbx", "rsi", "rdi")

Then you can export your graph to a GNN as a torch_geometric Data object:

data = graph_to_data(graph, reg_map)
assert data.x is not None

If you want to help your GNN to deal with different architectures, a helper is provided to encode registers as a mask, depending on their role regarding the calling convention:

from pcode_graph.gnn_exporter import map_calling_convention_registers

reg_map = map_registers(Arch.x86_64)
print(reg_map)
{'rax': tensor([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
 'rdx': tensor([0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
 'rdi': tensor([0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
 'rsi': tensor([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
 ...

Many options are provided by the graph_to_data function depending on how you want to export graph information into node and edges features. Look at the function documentation for more details.

Converting into networkx

In case you want to apply some classical graph-theory algorithms, you can use export_to_networkx from nx_exporter module:

def export_to_networkx(graph: CDG, ignore_cfg: bool) -> nx.MultiDiGraph: ...

Metadata

Release files for pcode-graph 4.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pcode-graph 4.1.0
File Size Uploaded Provenance Actions
pcode_graph-4.1.0.tar.gz 66.9 kB With provenance Details for pcode_graph-4.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pcode-graph 4.1.0
File Interpreter ABI Platform Actions
pcode_graph-4.1.0-py3-none-any.whl Python 3 none any Details for pcode_graph-4.1.0-py3-none-any.whl

Total release size: 124.4 kB

Release files / pcode_graph-4.1.0.tar.gz

Download URL pcode_graph-4.1.0.tar.gz
Size 66.9 kB
Tags Source
SHA-256 checksum
How to use checksums
9d4c9612812b15b86edc35684b8513ef3df28ed9538647a8f44658da8942348d
BLAKE2b-256 checksum
How to use checksums
a224070eac70eed2a2948e55ec2652a16767e302ec6df1eee239fa8b83b52460
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / pcode_graph-4.1.0-py3-none-any.whl

Download URL pcode_graph-4.1.0-py3-none-any.whl
Size 57.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8b149642365ff9e0273026f92a0b0962d0c0eab7e7022551072681748896999d
BLAKE2b-256 checksum
How to use checksums
0826aafe2cc851938ae072e0d2e5bdf1a744452a466bf4587f02c98a8f0ba4c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for pcode-graph 4.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pcode_graph-4.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page