feat(analysis): add control flow graph
This commit is contained in:
1 parent
696c7991ad
commit
42c16f3ce0
2 files changed
+216
No files matched your search
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public enum EdgeKind
|
||||||
|
{
|
||||||
|
Fallthrough,
|
||||||
|
Jump,
|
||||||
|
BranchTaken,
|
||||||
|
Call,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ControlFlowEdge
|
||||||
|
{
|
||||||
|
public ControlFlowEdge(ushort source, ushort target, EdgeKind kind)
|
||||||
|
{
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
this.kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort source { get; }
|
||||||
|
public ushort target { get; }
|
||||||
|
public EdgeKind kind { get; }
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
"0x" + source.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
+ " -> 0x" + target.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
+ " (" + kind + ")";
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public sealed class ControlFlowGraph
|
||||||
|
{
|
||||||
|
readonly Dictionary<ushort, BasicBlock> blockByAddress;
|
||||||
|
readonly List<ControlFlowEdge> edgeList;
|
||||||
|
readonly FlowTraversal traversal;
|
||||||
|
|
||||||
|
ControlFlowGraph(
|
||||||
|
ushort entry,
|
||||||
|
ushort baseAddress,
|
||||||
|
Dictionary<ushort, BasicBlock> blockByAddress,
|
||||||
|
List<ControlFlowEdge> edgeList,
|
||||||
|
FlowTraversal traversal)
|
||||||
|
{
|
||||||
|
this.entry = entry;
|
||||||
|
this.baseAddress = baseAddress;
|
||||||
|
this.blockByAddress = blockByAddress;
|
||||||
|
this.edgeList = edgeList;
|
||||||
|
this.traversal = traversal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort entry { get; }
|
||||||
|
public ushort baseAddress { get; }
|
||||||
|
|
||||||
|
public IReadOnlyCollection<BasicBlock> blocks => blockByAddress.Values;
|
||||||
|
public IReadOnlyList<ControlFlowEdge> edges => edgeList;
|
||||||
|
public IReadOnlyCollection<ushort> leaders => traversal.leaders;
|
||||||
|
public IReadOnlyList<ushort> indirectJumps => traversal.indirectJumps;
|
||||||
|
public IReadOnlyList<ushort> externalTargets => traversal.externalTargets;
|
||||||
|
|
||||||
|
public int blockCount => blockByAddress.Count;
|
||||||
|
public int edgeCount => edgeList.Count;
|
||||||
|
public int instructionCount => traversal.decoded.Count;
|
||||||
|
|
||||||
|
public IReadOnlyCollection<ushort> reachableAddresses => traversal.decoded.Keys.ToList();
|
||||||
|
|
||||||
|
public BasicBlock entryBlock => blockByAddress[entry];
|
||||||
|
|
||||||
|
public bool hasBlock(ushort address) => blockByAddress.ContainsKey(address);
|
||||||
|
|
||||||
|
public BasicBlock? blockAt(ushort address) =>
|
||||||
|
blockByAddress.TryGetValue(address, out var block) ? block : null;
|
||||||
|
|
||||||
|
public InstructionInfo? instructionAt(ushort address) =>
|
||||||
|
traversal.decoded.TryGetValue(address, out var info) ? info : null;
|
||||||
|
|
||||||
|
public bool isReachable(ushort address) => traversal.decoded.ContainsKey(address);
|
||||||
|
|
||||||
|
public IReadOnlyList<BasicBlock> blocksInOrder =>
|
||||||
|
blockByAddress.Values.OrderBy(block => block.startAddress).ToList();
|
||||||
|
|
||||||
|
public HashSet<ushort> reachableByteAddresses()
|
||||||
|
{
|
||||||
|
var bytes = new HashSet<ushort>();
|
||||||
|
foreach (var info in traversal.decoded.Values)
|
||||||
|
{
|
||||||
|
for (int offset = 0; offset < info.byteLength; offset++)
|
||||||
|
{
|
||||||
|
bytes.Add((ushort)(info.address + offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ControlFlowGraph build(byte[] program, ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
|
||||||
|
{
|
||||||
|
var traversal = FlowTraversal.explore(program, entry, baseAddress);
|
||||||
|
var blockByAddress = partition(program, traversal, baseAddress);
|
||||||
|
var edgeList = wire(program, blockByAddress, baseAddress);
|
||||||
|
return new ControlFlowGraph(entry, baseAddress, blockByAddress, edgeList, traversal);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Dictionary<ushort, BasicBlock> partition(byte[] program, FlowTraversal traversal, ushort baseAddress)
|
||||||
|
{
|
||||||
|
var blockByAddress = new Dictionary<ushort, BasicBlock>();
|
||||||
|
var decoded = traversal.decoded;
|
||||||
|
|
||||||
|
foreach (ushort leader in traversal.leaders)
|
||||||
|
{
|
||||||
|
if (!decoded.ContainsKey(leader))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var block = new BasicBlock(leader);
|
||||||
|
ushort address = leader;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var info = decoded[address];
|
||||||
|
block.add(info);
|
||||||
|
|
||||||
|
if (info.isTerminator)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort next = info.nextAddress;
|
||||||
|
if (!decoded.ContainsKey(next) || traversal.leaders.Contains(next))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
address = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockByAddress[leader] = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockByAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ControlFlowEdge> wire(byte[] program, Dictionary<ushort, BasicBlock> blockByAddress, ushort baseAddress)
|
||||||
|
{
|
||||||
|
var edgeList = new List<ControlFlowEdge>();
|
||||||
|
|
||||||
|
foreach (var block in blockByAddress.Values)
|
||||||
|
{
|
||||||
|
var last = block.terminator;
|
||||||
|
switch (last.flow)
|
||||||
|
{
|
||||||
|
case FlowEffect.Jump:
|
||||||
|
link(blockByAddress, edgeList, block, last.jumpTarget!.Value, EdgeKind.Jump);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FlowEffect.Branch:
|
||||||
|
{
|
||||||
|
ushort notTaken = last.nextAddress;
|
||||||
|
ushort taken = (ushort)(notTaken + InstructionDecoder.byteLengthAt(program, notTaken, baseAddress));
|
||||||
|
link(blockByAddress, edgeList, block, notTaken, EdgeKind.Fallthrough);
|
||||||
|
link(blockByAddress, edgeList, block, taken, EdgeKind.BranchTaken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FlowEffect.Call:
|
||||||
|
link(blockByAddress, edgeList, block, last.callTarget!.Value, EdgeKind.Call);
|
||||||
|
link(blockByAddress, edgeList, block, last.nextAddress, EdgeKind.Fallthrough);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FlowEffect.Fallthrough:
|
||||||
|
link(blockByAddress, edgeList, block, last.nextAddress, EdgeKind.Fallthrough);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FlowEffect.Return:
|
||||||
|
case FlowEffect.Terminate:
|
||||||
|
case FlowEffect.IndirectJump:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edgeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void link(
|
||||||
|
Dictionary<ushort, BasicBlock> blockByAddress,
|
||||||
|
List<ControlFlowEdge> edgeList,
|
||||||
|
BasicBlock source,
|
||||||
|
ushort target,
|
||||||
|
EdgeKind kind)
|
||||||
|
{
|
||||||
|
if (!blockByAddress.TryGetValue(target, out var targetBlock))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var edge = new ControlFlowEdge(source.startAddress, target, kind);
|
||||||
|
source.addSuccessor(edge);
|
||||||
|
targetBlock.addPredecessor(source.startAddress);
|
||||||
|
edgeList.Add(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string render()
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append("control flow graph: entry 0x").Append(entry.ToString("X4", CultureInfo.InvariantCulture));
|
||||||
|
builder.Append(", ").Append(blockCount).Append(" blocks, ").Append(edgeCount).Append(" edges\n");
|
||||||
|
foreach (var block in blocksInOrder)
|
||||||
|
{
|
||||||
|
builder.Append(block.render());
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user