feat(analysis): add flow traversal and reachability analysis
This commit is contained in:
1 parent
42c16f3ce0
commit
1f3ed9595e
3 files changed
+219
No files matched your search
@@ -0,0 +1,12 @@
|
||||
namespace Chip8.Analysis;
|
||||
|
||||
public enum FlowEffect
|
||||
{
|
||||
Fallthrough,
|
||||
Branch,
|
||||
Jump,
|
||||
Call,
|
||||
Return,
|
||||
IndirectJump,
|
||||
Terminate,
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Chip8.Analysis;
|
||||
|
||||
public sealed class FlowTraversal
|
||||
{
|
||||
FlowTraversal(
|
||||
ushort entry,
|
||||
ushort baseAddress,
|
||||
IReadOnlyDictionary<ushort, InstructionInfo> decoded,
|
||||
IReadOnlyCollection<ushort> leaders,
|
||||
IReadOnlyList<ushort> indirectJumps,
|
||||
IReadOnlyList<ushort> externalTargets)
|
||||
{
|
||||
this.entry = entry;
|
||||
this.baseAddress = baseAddress;
|
||||
this.decoded = decoded;
|
||||
this.leaders = leaders;
|
||||
this.indirectJumps = indirectJumps;
|
||||
this.externalTargets = externalTargets;
|
||||
}
|
||||
|
||||
public ushort entry { get; }
|
||||
public ushort baseAddress { get; }
|
||||
public IReadOnlyDictionary<ushort, InstructionInfo> decoded { get; }
|
||||
public IReadOnlyCollection<ushort> leaders { get; }
|
||||
public IReadOnlyList<ushort> indirectJumps { get; }
|
||||
public IReadOnlyList<ushort> externalTargets { get; }
|
||||
|
||||
public static FlowTraversal explore(byte[] program, ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
|
||||
{
|
||||
var decoded = new Dictionary<ushort, InstructionInfo>();
|
||||
var leaders = new SortedSet<ushort> { entry };
|
||||
var indirectJumps = new List<ushort>();
|
||||
var externalTargets = new SortedSet<ushort>();
|
||||
var pending = new Queue<ushort>();
|
||||
var seen = new HashSet<ushort>();
|
||||
pending.Enqueue(entry);
|
||||
|
||||
while (pending.Count > 0)
|
||||
{
|
||||
ushort address = pending.Dequeue();
|
||||
if (!seen.Add(address))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var info = InstructionDecoder.decodeAt(program, address, baseAddress);
|
||||
if (info is null)
|
||||
{
|
||||
externalTargets.Add(address);
|
||||
continue;
|
||||
}
|
||||
|
||||
decoded[address] = info;
|
||||
|
||||
switch (info.flow)
|
||||
{
|
||||
case FlowEffect.Fallthrough:
|
||||
enqueue(pending, info.nextAddress);
|
||||
break;
|
||||
|
||||
case FlowEffect.Branch:
|
||||
{
|
||||
ushort notTaken = info.nextAddress;
|
||||
ushort taken = (ushort)(notTaken + InstructionDecoder.byteLengthAt(program, notTaken, baseAddress));
|
||||
leaders.Add(notTaken);
|
||||
leaders.Add(taken);
|
||||
enqueue(pending, notTaken);
|
||||
enqueue(pending, taken);
|
||||
break;
|
||||
}
|
||||
|
||||
case FlowEffect.Jump:
|
||||
leaders.Add(info.jumpTarget!.Value);
|
||||
enqueue(pending, info.jumpTarget!.Value);
|
||||
break;
|
||||
|
||||
case FlowEffect.Call:
|
||||
{
|
||||
ushort target = info.callTarget!.Value;
|
||||
ushort returnAddress = info.nextAddress;
|
||||
leaders.Add(target);
|
||||
leaders.Add(returnAddress);
|
||||
enqueue(pending, target);
|
||||
enqueue(pending, returnAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
case FlowEffect.IndirectJump:
|
||||
indirectJumps.Add(address);
|
||||
break;
|
||||
|
||||
case FlowEffect.Return:
|
||||
case FlowEffect.Terminate:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var indirectList = new List<ushort>(indirectJumps);
|
||||
indirectList.Sort();
|
||||
return new FlowTraversal(entry, baseAddress, decoded, leaders, indirectList, new List<ushort>(externalTargets));
|
||||
}
|
||||
|
||||
static void enqueue(Queue<ushort> pending, ushort address) => pending.Enqueue(address);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Chip8.Analysis;
|
||||
|
||||
public sealed class ReachabilitySet
|
||||
{
|
||||
readonly SortedSet<ushort> reachable;
|
||||
readonly FlowTraversal traversal;
|
||||
|
||||
ReachabilitySet(ushort entry, ushort baseAddress, int programLength, FlowTraversal traversal)
|
||||
{
|
||||
this.entry = entry;
|
||||
this.baseAddress = baseAddress;
|
||||
this.programLength = programLength;
|
||||
this.traversal = traversal;
|
||||
reachable = new SortedSet<ushort>(traversal.decoded.Keys);
|
||||
}
|
||||
|
||||
public ushort entry { get; }
|
||||
public ushort baseAddress { get; }
|
||||
public int programLength { get; }
|
||||
|
||||
public IReadOnlyCollection<ushort> reachableAddresses => reachable;
|
||||
|
||||
public int reachableInstructionCount => reachable.Count;
|
||||
|
||||
public bool contains(ushort address) => reachable.Contains(address);
|
||||
|
||||
public int reachableByteCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int total = 0;
|
||||
foreach (var info in traversal.decoded.Values)
|
||||
{
|
||||
total += info.byteLength;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
public int programByteCount => programLength;
|
||||
|
||||
public double reachableByteFraction =>
|
||||
programLength == 0 ? 0.0 : (double)reachableByteCount / programLength;
|
||||
|
||||
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 IReadOnlyList<InstructionInfo> deadInstructions(byte[] program)
|
||||
{
|
||||
var linear = InstructionDecoder.decodeLinear(program, baseAddress);
|
||||
return linear.Where(info => !reachable.Contains(info.address)).ToList();
|
||||
}
|
||||
|
||||
public IReadOnlyList<ushort> dataByteAddresses()
|
||||
{
|
||||
var covered = reachableByteAddresses();
|
||||
var data = new List<ushort>();
|
||||
for (int offset = 0; offset < programLength; offset++)
|
||||
{
|
||||
ushort address = (ushort)(baseAddress + offset);
|
||||
if (!covered.Contains(address))
|
||||
{
|
||||
data.Add(address);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static ReachabilitySet build(byte[] program, ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
|
||||
{
|
||||
var traversal = FlowTraversal.explore(program, entry, baseAddress);
|
||||
return new ReachabilitySet(entry, baseAddress, program.Length, traversal);
|
||||
}
|
||||
|
||||
public string render()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("reachable instructions: ").Append(reachableInstructionCount);
|
||||
builder.Append(" (").Append(reachableByteCount).Append(" of ").Append(programLength).Append(" bytes)\n");
|
||||
foreach (ushort address in reachable)
|
||||
{
|
||||
builder.Append(" 0x").Append(address.ToString("X4", CultureInfo.InvariantCulture)).Append('\n');
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user