2025-07-27 11:45:49 -05:00
|
|
|
using Microsoft.Iris.Asm.Models;
|
|
|
|
|
using Microsoft.Iris.Markup;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace Microsoft.Iris.DecompXml;
|
|
|
|
|
|
2025-09-24 18:08:14 -05:00
|
|
|
public class ControlFlowAnalyzer
|
2025-07-27 11:45:49 -05:00
|
|
|
{
|
|
|
|
|
private static readonly ImmutableHashSet<OpCode> _jumpOpCodes = [
|
|
|
|
|
OpCode.JumpIfFalse, OpCode.JumpIfFalsePeek, OpCode.JumpIfTruePeek,
|
|
|
|
|
OpCode.JumpIfDictionaryContains, OpCode.JumpIfNullPeek, OpCode.Jump,
|
|
|
|
|
];
|
|
|
|
|
|
2025-07-27 13:34:56 -05:00
|
|
|
private static readonly ImmutableHashSet<OpCode> _unconditionalJumpOpCodes = [
|
|
|
|
|
OpCode.ReturnValue, OpCode.ReturnVoid, OpCode.Jump,
|
|
|
|
|
];
|
|
|
|
|
|
2025-09-24 18:08:14 -05:00
|
|
|
public ControlFlowAnalyzer(Instruction[] instructions)
|
|
|
|
|
{
|
|
|
|
|
ControlBlocks = CreateGraph(instructions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<IProgramBlock> ControlBlocks { get; }
|
|
|
|
|
|
|
|
|
|
public IProgramBlock GetByOffset(uint offset)
|
|
|
|
|
{
|
|
|
|
|
return ControlBlocks.First(b => offset >= b.StartOffset && offset <= b.EndOffset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IProgramBlock GetByStartOffset(uint offset) => ControlBlocks.FirstOrDefault(b => offset == b.StartOffset);
|
|
|
|
|
|
|
|
|
|
public IProgramBlock GetByInstruction(Instruction instruction) => GetByOffset(instruction.Offset);
|
|
|
|
|
|
|
|
|
|
public bool IsAlwaysExecuted(uint offset)
|
|
|
|
|
{
|
|
|
|
|
if (ControlBlocks.Count == 1)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Checks if the entry node is post-dominated by the block containing this offset.
|
|
|
|
|
// Essentially, do all paths through this method execute code at this offset?
|
|
|
|
|
|
|
|
|
|
var blockOfInterest = GetByStartOffset(offset);
|
|
|
|
|
if (blockOfInterest is null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
HashSet<uint> exitBlockOffsets = new(ControlBlocks
|
|
|
|
|
.Where(b => b.Body[^1].OpCode is OpCode.ReturnValue or OpCode.ReturnVoid)
|
|
|
|
|
.Select(b => b.StartOffset));
|
|
|
|
|
|
|
|
|
|
HashSet<uint> visitedStartOffsets = [];
|
|
|
|
|
Stack<IProgramBlock> stack = [];
|
|
|
|
|
|
|
|
|
|
stack.Push(ControlBlocks[0]);
|
|
|
|
|
|
|
|
|
|
while (stack.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var current = stack.Pop();
|
|
|
|
|
var currentOffset = current.StartOffset;
|
|
|
|
|
|
|
|
|
|
if (currentOffset == offset || visitedStartOffsets.Contains(currentOffset))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (exitBlockOffsets.Contains(currentOffset))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
visitedStartOffsets.Add(current.StartOffset);
|
|
|
|
|
|
|
|
|
|
foreach (var childOffset in current.GetChildrenStartOffsets())
|
|
|
|
|
{
|
|
|
|
|
var child = GetByStartOffset(childOffset);
|
|
|
|
|
stack.Push(child);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static List<IProgramBlock> CreateGraph(Instruction[] instructions)
|
2025-07-27 11:45:49 -05:00
|
|
|
{
|
|
|
|
|
// See slide 15 (page 8) of https://www.cs.utexas.edu/~lin/cs380c/handout03.pdf
|
|
|
|
|
|
|
|
|
|
var procedureMap = instructions.ToImmutableDictionary(i => i.Offset);
|
|
|
|
|
|
|
|
|
|
// Pass I: Find the start of each block
|
|
|
|
|
HashSet<uint> leaderOffsets = [instructions[0].Offset];
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < instructions.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var instruction = instructions[i];
|
|
|
|
|
|
|
|
|
|
if (!_jumpOpCodes.Contains(instruction.OpCode))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
var jumpOffset = (uint)instruction.Operands.First().Value;
|
|
|
|
|
leaderOffsets.Add(jumpOffset);
|
|
|
|
|
|
|
|
|
|
if (instruction.OpCode is not OpCode.Jump)
|
|
|
|
|
leaderOffsets.Add(instructions[i + 1].Offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pass II: Segment the procedure so a leader starts each block
|
2025-09-24 10:34:28 -05:00
|
|
|
List<IProgramBlock> blocks = [];
|
2025-07-27 11:45:49 -05:00
|
|
|
|
|
|
|
|
foreach (var leaderOffset in leaderOffsets)
|
|
|
|
|
{
|
|
|
|
|
List<Instruction> body = [procedureMap[leaderOffset]];
|
|
|
|
|
|
|
|
|
|
foreach (var instruction in instructions.SkipWhile(i => i.Offset <= leaderOffset))
|
|
|
|
|
{
|
|
|
|
|
if (leaderOffsets.Contains(instruction.Offset))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
body.Add(instruction);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 13:34:56 -05:00
|
|
|
var lastInstruction = body[^1];
|
|
|
|
|
|
|
|
|
|
var jumpOffset = _jumpOpCodes.Contains(lastInstruction.OpCode)
|
|
|
|
|
? (uint)lastInstruction.Operands.First().Value
|
|
|
|
|
: uint.MaxValue;
|
|
|
|
|
|
|
|
|
|
var nextOffset = uint.MaxValue;
|
|
|
|
|
if (!_unconditionalJumpOpCodes.Contains(lastInstruction.OpCode))
|
|
|
|
|
{
|
|
|
|
|
nextOffset = instructions
|
|
|
|
|
.SkipWhile(i => i.Offset <= lastInstruction.Offset)
|
|
|
|
|
.FirstOrDefault()?.Offset
|
|
|
|
|
?? uint.MaxValue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 10:34:28 -05:00
|
|
|
BasicControlFlowBlock block = new(leaderOffset, lastInstruction.Offset, body, nextOffset, jumpOffset);
|
2025-07-27 11:45:49 -05:00
|
|
|
blocks.Add(block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return blocks;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 18:08:14 -05:00
|
|
|
public string SerializeToGraphviz()
|
2025-09-24 10:34:28 -05:00
|
|
|
{
|
2025-09-24 18:08:14 -05:00
|
|
|
var sortedBlocks = ControlBlocks.OrderBy(b => b.StartOffset).ToArray();
|
2025-07-27 11:45:49 -05:00
|
|
|
StringBuilder sb = new();
|
|
|
|
|
|
|
|
|
|
sb.AppendLine("digraph G {");
|
2025-07-27 13:34:56 -05:00
|
|
|
sb.AppendLine(" splines=polyline;");
|
|
|
|
|
sb.AppendLine(" node [shape=none];");
|
2025-07-27 11:45:49 -05:00
|
|
|
|
2025-07-27 13:34:56 -05:00
|
|
|
var rank = 0;
|
2025-09-24 18:08:14 -05:00
|
|
|
foreach (var block in sortedBlocks)
|
2025-07-27 13:34:56 -05:00
|
|
|
{
|
|
|
|
|
var nodeId = block.StartOffset;
|
2025-07-27 11:45:49 -05:00
|
|
|
|
2025-07-27 13:34:56 -05:00
|
|
|
sb.AppendLine($" {nodeId} [label=<");
|
|
|
|
|
sb.AppendLine($" <table border=\"0\" cellborder=\"1\" cellspacing=\"0\">");
|
|
|
|
|
|
|
|
|
|
foreach (var instruction in block.Body)
|
|
|
|
|
{
|
|
|
|
|
sb.AppendLine($" <tr><td align=\"left\">0x{instruction.Offset:X4}</td><td align=\"left\">{instruction}</td></tr>");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.AppendLine($" </table>");
|
|
|
|
|
sb.AppendLine($" >];");
|
|
|
|
|
|
|
|
|
|
if (block.NextOffset is not uint.MaxValue)
|
2025-08-04 21:41:16 -05:00
|
|
|
sb.AppendLine($" {nodeId}:s -> {block.NextOffset}:n;");
|
2025-07-27 13:34:56 -05:00
|
|
|
|
2025-09-24 10:34:28 -05:00
|
|
|
if (block is BasicControlFlowBlock { BranchTargetOffset: not uint.MaxValue } cfBlock)
|
|
|
|
|
sb.AppendLine($" {nodeId}:s -> {cfBlock.BranchTargetOffset}:n [color=red];");
|
2025-07-27 13:34:56 -05:00
|
|
|
}
|
2025-07-27 11:45:49 -05:00
|
|
|
|
|
|
|
|
sb.AppendLine("}");
|
|
|
|
|
|
|
|
|
|
return sb.ToString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 10:34:28 -05:00
|
|
|
public interface IProgramBlock
|
|
|
|
|
{
|
|
|
|
|
uint StartOffset { get; }
|
|
|
|
|
uint EndOffset { get; }
|
|
|
|
|
List<Instruction> Body { get; }
|
2025-09-24 17:46:17 -05:00
|
|
|
uint NextOffset { get; }
|
2025-09-24 10:34:28 -05:00
|
|
|
|
2025-09-24 17:46:17 -05:00
|
|
|
IEnumerable<uint> GetChildrenStartOffsets();
|
2025-09-24 10:34:28 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-24 17:46:17 -05:00
|
|
|
public abstract record ProgramBlock(uint StartOffset, uint EndOffset, List<Instruction> Body, uint NextOffset = uint.MaxValue)
|
2025-09-24 10:34:28 -05:00
|
|
|
: IProgramBlock
|
|
|
|
|
{
|
2025-09-24 17:46:17 -05:00
|
|
|
public virtual IEnumerable<uint> GetChildrenStartOffsets()
|
|
|
|
|
{
|
|
|
|
|
if (NextOffset is not uint.MaxValue)
|
|
|
|
|
yield return NextOffset;
|
|
|
|
|
}
|
2025-09-24 10:34:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public record BasicControlFlowBlock(uint StartOffset, uint EndOffset, List<Instruction> Body,
|
2025-09-24 17:46:17 -05:00
|
|
|
uint NextOffset = uint.MaxValue, uint BranchTargetOffset = uint.MaxValue)
|
|
|
|
|
: ProgramBlock(StartOffset, EndOffset, Body, NextOffset)
|
2025-09-24 10:34:28 -05:00
|
|
|
{
|
2025-09-24 17:46:17 -05:00
|
|
|
public override IEnumerable<uint> GetChildrenStartOffsets()
|
2025-09-24 10:34:28 -05:00
|
|
|
{
|
2025-09-24 17:46:17 -05:00
|
|
|
foreach (var offset in base.GetChildrenStartOffsets())
|
|
|
|
|
yield return offset;
|
2025-09-24 10:34:28 -05:00
|
|
|
|
2025-09-24 17:46:17 -05:00
|
|
|
if (BranchTargetOffset is not uint.MaxValue)
|
|
|
|
|
yield return BranchTargetOffset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class IProgramBlockExtensions
|
|
|
|
|
{
|
|
|
|
|
public static bool HasEdgeTo(this IProgramBlock block, IProgramBlock targetBlock, List<IProgramBlock> blocks)
|
2025-09-24 10:34:28 -05:00
|
|
|
{
|
2025-09-24 17:46:17 -05:00
|
|
|
return block
|
|
|
|
|
.GetChildren(blocks)
|
|
|
|
|
.Contains(targetBlock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static IEnumerable<IProgramBlock> GetChildren(this IProgramBlock block, List<IProgramBlock> blocks)
|
|
|
|
|
{
|
|
|
|
|
foreach (var startOffset in block.GetChildrenStartOffsets())
|
|
|
|
|
yield return blocks.First(b => b.StartOffset == startOffset);
|
2025-09-24 10:34:28 -05:00
|
|
|
}
|
|
|
|
|
}
|