Table of Contents

Control Flow Graphs (CFG)

A control flow graph (CFG) is a graph where each node corresponds to one basic block of code, and each edge represents a possible control flow transfer. This way, a control flow graph tries to encode all possible execution paths in a chunk of code, method or entire program.

The figure below depicts a control flow graph of a very simple if-statement:

Graphs

Every control flow graph is represented using the ControlFlowGraph<TInstruction> class.

A new, empty graph can be created by using one of the constructors.

var cfg = new ControlFlowGraph<TInstruction>();

To extract a control flow graph from an existing code stream, this depends per platform. Refer to the platform-specific documentation for more details.

Nodes

Nodes in a control flow graph represent the individual basic blocks in the code, and are implemented by the ControlFlowNode<TInstruction> class. They can be accessed from the Nodes property, which can be iterated:

ControlFlowGraph<TInstruction> cfg = ...;

// Iterate over all nodes in a control flow graph:
foreach (var node in cfg.Nodes)
    Console.WriteLine($"{node.Offset:X8}");

Individual nodes can be obtained by looking them up by offset:

var node = cfg.Nodes.GetByOffset(offset: 0x1234);

This performs a linear search through all the nodes, and finds the first basic block that matches. To ensure all nodes have updated offsets according to their contents, use the UpdateOffsets method:

cfg.Nodes.UpdateOffsets();

When doing many lookups by offset, consider first creating an offset map for faster lookups.

var offsetMap = cfg.Nodes.CreateOffsetMap();
var n1 = offsetMap[0x0001];
var n2 = offsetMap[0x0004];
var n3 = offsetMap[0x0010];

Every node exposes a basic block containing the instructions it executes:

ControlFlowNode<TInstruction> node = ...;

// Iterate over all instructions within the block.
foreach (var instruction in node.Contents.Instructions) 
    Console.WriteLine(instruction);

Edges

Nodes are connected to each other with edges, represented by the ControlFlowEdge<TInstruction> class.

There are four types of edges Echo distinguishes:

Type Description
FallThrough An edge implicitly introduced by a block that falls through into the next one.
Unconditional An edge introduced by an unconditional jump/branch instruction.
Conditional An edge introduced by a conditional jump/branch instruction.
Abnormal An edge introduced by special or exceptional control flow (such as an error or signal).

Individual edges can be obtained by accessing their respective properties:

ControlFlowNode<TInstruction> node = ...;

// Obtain the outgoing unconditional or fallthrough edge (if available).
var unconditional = node.UnconditionalEdge; 
if (unconditional is not null)
    Console.WriteLine(unconditional);

// Iterate all outgoing conditional edges.
foreach (var conditional in node.ConditionalEdges)
    Console.WriteLine(conditional);

// Iterate all outgoing abnormal edges.
foreach (var abnormal in node.AbnormalEdges)
    Console.WriteLine(abnormal);

Every edge defines a Source and a Target node, allowing for traversing the graph.

ControlFlowEdge<TInstruction> edge = ...;
ControlFlowNode<TInstruction> target = edge.Target;

All outgoing edges can also be obtained at once using GetOutgoingEdges():

foreach (var edge in node.GetOutgoingEdges())
    Console.WriteLine(edge.Target);

If only interested in the target nodes, GetSuccessors() can be used instead:

foreach (var successor in node.GetSuccessors())
    Console.WriteLine(successor);
```[dfg-basics.md](dfg-basics.md)

Similarly, incoming edges can also be obtained using `GetIncomingEdges()` and `GetPredecessors()`:

```csharp
foreach (var edge in node.GetIncomingEdges())
    Console.WriteLine(edge.Source);
foreach (var predecessor in node.GetPredecessors())
    Console.WriteLine(predecessor);

New edges can be drawn by either mutating the outgoing edges properties, or by using the ConnectWith helper method:

ControlFlowNode<TInstruction> node1 = ...;
ControlFlowNode<TInstruction> node2 = ...;
ControlFlowNode<TInstruction> node3 = ...;
ControlFlowNode<TInstruction> node4 = ...;

node1.ConnectWith(node2);
node2.ConnectWith(node3, ControlFlowEdgeType.Conditional);
node2.ConnectWith(node4, ControlFlowEdgeType.FallThrough);

Regions

Control flow graphs can be subdivided into regions. These can either be simple scopes, but also regions protected by an exception handler.

foreach (var region in cfg.Regions)
{
    // ...
}

There are various types of regions:

Type Description
ScopeRegion A simple collection of nodes.
ExceptionHandlerRegion A region that is protected by one or more exception handlers.
HandlerRegion A single exception handler.

Invidiual nodes can be put into a ScopeRegion:

// Define new scope and add it to the graph.
var region = new ScopeRegion<TInstruction>();
cfg.Regions.Add(region);

// Add nodes.
region.Nodes.Add(node1);
region.Nodes.Add(node2);
region.Nodes.Add(node3);

// Define the entry point of the scope.
region.EntryPoint = node1;

Exception handlers comprise multiple regions:

// Define a new exception handler.
var main = new ExceptionHandlerRegion<TInstruction>();
main.Handlers.Add(handler);

// Add a node to the protected region.
main.ProtectedRegion.Nodes.Add(node1);
main.ProtectedRegion.EntryPoint = node1;

// Add a handler.
var handler = new HandlerRegion<TInstruction>();
handler.Nodes.Add(node2);
handler.EntryPoint = node2;

Nodes in a control flow graph are always part of a region. By default, they are part of the root region, which is the control flow graph itself. Obtaining the region a node is situated in can be done using the ParentRegion property, or GetSituatedRegions method to get all the regions it is present in:

ControlFlowNode<TInstruction> node = ...

var directParentRegion = node.ParentRegion;
var allRegions = node.GetSituatedRegions();

Testing whether a node belongs to a specific region (including sub regions) can be done using the IsInRegion method:

if (node.IsInRegion(parentRegion))
{
    // ...
}

Visualizing Control Flow Graphs

Echo provides default serializers for graphs in DOT format.

using var writer = File.CreateText("output.dot");
cfg.ToDotGraph(writer);

To customize the way instructions are formatted, use an IInstructionFormatter<TInstruction>:

class MyFormatter : IInstructionFormatter<TInstruction>
{
    public string Format(in TInstruction instruction)
    {
        // ...
    }
}

using var writer = File.CreateText("output.dot");
cfg.ToDotGraph(writer, new MyFormatter());

These can then be visualized using e.g., GraphViz.