[WIP] If-elseif-else clause decompilation

This commit is contained in:
Yoshi Askharoun
2025-09-24 10:34:28 -05:00
parent db5e92beeb
commit 7f83b92fcd
3 changed files with 125 additions and 44 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Microsoft.Iris.DecompXml;
internal class CodeBlockInfo
internal record CodeBlockInfo
{
public CodeBlockInfo(uint startOffset, uint endOffset, SyntaxKind kind, ExpressionSyntax expression = null)
{
+66 -9
View File
@@ -18,7 +18,7 @@ public static class ControlFlowAnalyzer
OpCode.ReturnValue, OpCode.ReturnVoid, OpCode.Jump,
];
public static List<ControlFlowBlock> CreateGraph(Instruction[] instructions)
public static List<IProgramBlock> CreateGraph(Instruction[] instructions)
{
// See slide 15 (page 8) of https://www.cs.utexas.edu/~lin/cs380c/handout03.pdf
@@ -42,7 +42,7 @@ public static class ControlFlowAnalyzer
}
// Pass II: Segment the procedure so a leader starts each block
List<ControlFlowBlock> blocks = [];
List<IProgramBlock> blocks = [];
foreach (var leaderOffset in leaderOffsets)
{
@@ -71,14 +71,14 @@ public static class ControlFlowAnalyzer
?? uint.MaxValue;
}
ControlFlowBlock block = new(leaderOffset, lastInstruction.Offset, body, nextOffset, jumpOffset);
BasicControlFlowBlock block = new(leaderOffset, lastInstruction.Offset, body, nextOffset, jumpOffset);
blocks.Add(block);
}
// Pass III: Resolve next and branch target blocks
for (var b = 0; b < blocks.Count; b++)
{
var block = blocks[b];
var block = (BasicControlFlowBlock)blocks[b];
if (block.NextOffset is not uint.MaxValue)
block = block with { Next = blocks[IndexOfBlockFromStartOffset(block.NextOffset)] };
@@ -104,7 +104,21 @@ public static class ControlFlowAnalyzer
}
}
public static string SerializeToGraphviz(IEnumerable<ControlFlowBlock> blocks)
public static List<IProgramBlock> CollapseBlocks(this List<IProgramBlock> blocks)
{
for (int b = 0; b < blocks.Count; b++)
{
var block = blocks[b];
if (block.Body[^1].OpCode is OpCode.JumpIfFalse)
{
// If conditions always end with JMPF
}
}
return [];
}
public static string SerializeToGraphviz(IEnumerable<IProgramBlock> blocks)
{
var sortedBlocks = blocks.OrderBy(b => b.StartOffset).ToArray();
StringBuilder sb = new();
@@ -132,8 +146,8 @@ public static class ControlFlowAnalyzer
if (block.NextOffset is not uint.MaxValue)
sb.AppendLine($" {nodeId}:s -> {block.NextOffset}:n;");
if (block.BranchTargetOffset is not uint.MaxValue)
sb.AppendLine($" {nodeId}:s -> {block.BranchTargetOffset}:n [color=red];");
if (block is BasicControlFlowBlock { BranchTargetOffset: not uint.MaxValue } cfBlock)
sb.AppendLine($" {nodeId}:s -> {cfBlock.BranchTargetOffset}:n [color=red];");
}
sb.AppendLine("}");
@@ -142,6 +156,49 @@ public static class ControlFlowAnalyzer
}
}
public record ControlFlowBlock(uint StartOffset, uint EndOffset, List<Instruction> Body,
public interface IProgramBlock
{
uint StartOffset { get; }
uint EndOffset { get; }
uint NextOffset { get; }
List<Instruction> Body { get; }
IProgramBlock Next { get; }
bool HasEdgeTo(IProgramBlock block);
}
public abstract record ProgramBlock(uint StartOffset, uint EndOffset, List<Instruction> Body,
uint NextOffset = uint.MaxValue, IProgramBlock Next = null)
: IProgramBlock
{
public virtual bool HasEdgeTo(IProgramBlock block) => NextOffset == block.StartOffset;
}
public record BasicControlFlowBlock(uint StartOffset, uint EndOffset, List<Instruction> Body,
uint NextOffset = uint.MaxValue, uint BranchTargetOffset = uint.MaxValue,
ControlFlowBlock Next = null, ControlFlowBlock BranchTarget = null);
IProgramBlock Next = null, IProgramBlock BranchTarget = null)
: ProgramBlock(StartOffset, EndOffset, Body, NextOffset, Next)
{
public override bool HasEdgeTo(IProgramBlock block) => base.HasEdgeTo(block) || BranchTargetOffset == block.StartOffset;
}
public record ConditionalControlFlowBlock(uint StartOffset, uint EndOffset, List<Instruction> Body,
uint NextOffset = uint.MaxValue, IProgramBlock Next = null,
IProgramBlock IfBlock = null, List<IProgramBlock> ElseIfBlocks = null, IProgramBlock ElseBlock = null)
: ProgramBlock(StartOffset, EndOffset, Body, NextOffset, Next)
{
public ConditionalControlFlowBlock(IProgramBlock programBlock,
IProgramBlock ifBlock = null, List<IProgramBlock> elseIfBlocks = null, IProgramBlock elseBlock = null)
: this(programBlock.StartOffset, programBlock.EndOffset, programBlock.Body,
programBlock.NextOffset, programBlock.Next, ifBlock, elseIfBlocks ?? [], elseBlock)
{
}
public override bool HasEdgeTo(IProgramBlock block)
{
return base.HasEdgeTo(block)
|| IfBlock.StartOffset == block.StartOffset
|| ElseIfBlocks.Any(b => b.StartOffset == block.StartOffset)
|| ElseBlock.StartOffset == block.StartOffset;
}
}
+58 -34
View File
@@ -34,9 +34,22 @@ partial class Decompiler
var dotGraph = ControlFlowAnalyzer.SerializeToGraphviz(controlBlocks);
Console.WriteLine(dotGraph);
// TODO: Search for loops
// TODO: Search for branch conditions
var collapsedBlocks = controlBlocks.CollapseBlocks();
Stack<CodeBlockInfo> blockStack = [];
blockStack.Push(new(0, methodBody[^1].Offset, SyntaxKind.Block, null));
HashSet<uint> jumpFalseToOffsets = new(methodBody
.Where(i => i.OpCode is OpCode.JumpIfFalse)
.Select(i => (uint)i.Operands.First().Value));
HashSet<uint> jumpToOffsets = new(methodBody
.Where(i => i.OpCode is OpCode.Jump)
.Select(i => (uint)i.Operands.First().Value));
HashSet<string> scopedLocals = [];
Stack<object> stack = new();
@@ -44,20 +57,33 @@ partial class Decompiler
{
var instruction = methodBody[i];
// TODO: Handle for loops
if (instruction.Offset == blockStack.Peek().EndOffset)
if (jumpFalseToOffsets.Contains(instruction.Offset))
{
// Make sure there is only one top-level block
if (blockStack.Count > 1)
var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset };
currentBlock.FinalizeBlock(blockStack.Peek());
// This address marks the end of the affirmative branch of an IF clause.
// If multiple blocks lead to this address, then we're outside of the IF clause entirely.
// Otherwise, it's probably the start of an ELSE clause.
var currentControlBlock = controlBlocks.First(b => instruction.Offset >= b.StartOffset && instruction.Offset <= b.EndOffset);
if (controlBlocks.Count(b => b.HasEdgeTo(currentControlBlock)) <= 1)
{
var currentBlock = blockStack.Pop();
blockStack.Push(new(instruction.Offset, uint.MaxValue, SyntaxKind.ElseClause, null));
}
}
if (jumpToOffsets.Contains(instruction.Offset))
{
// This address is code that will be unconditionally executed. For now,
// we'll assume that this is the end of IF/ELSE clauses.
while (blockStack.Count > 1)
{
if (blockStack.Peek().Kind is SyntaxKind.ElseClause)
{
var currentBlock = blockStack.Pop() with { EndOffset = instruction.Offset };
currentBlock.FinalizeBlock(blockStack.Peek());
}
else
{
// End of function
if (i + 1 != methodBody.Length)
throw new InvalidOperationException("Expected end of function!");
}
}
@@ -126,6 +152,8 @@ partial class Decompiler
.WithInitializer(EqualsValueClause(newSymbolValueExpr))
)
));
scopedLocals.Add(symbolRef.Symbol);
}
else
{
@@ -181,38 +209,34 @@ partial class Decompiler
}
var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek or OpCode.JumpIfNullPeek;
var rawJumpCondition = IrisExpression.ToSyntax(isPeek ? stack.Peek() : stack.Pop(), _context);
var jumpCondition = IrisExpression.ToSyntax(isPeek ? stack.Peek() : stack.Pop(), _context);
// TODO: Invert jump condition when decompiling to blocks instead of gotos
var jumpCondition = opCode switch
if (opCode is OpCode.JumpIfFalse)
{
OpCode.JumpIfFalse or
OpCode.JumpIfFalsePeek => LogicalNotOf(rawJumpCondition),
OpCode.JumpIfTruePeek => rawJumpCondition,
OpCode.JumpIfNullPeek => BinaryExpression(SyntaxKind.EqualsEqualsToken,
rawJumpCondition,
LiteralExpression(SyntaxKind.NullLiteralExpression)),
_ => throw new NotImplementedException()
};
//var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, SyntaxKind.IfStatement, jumpCondition);
//blockStack.Push(ifBlock);
var ifBlock = IfStatement(SimplifyExpression(jumpCondition),
GotoStatement(SyntaxKind.GotoStatement, IdentifierName($"UIB_{jumpToOffset:X4}")))
.WithLeadingTrivia(Comment($"/* UIB_{instruction.Offset:X4} */"));
blockStack.Peek().Statements.Add(ifBlock);
// JMPF is used to evaluate the branch condition
var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, SyntaxKind.IfStatement, jumpCondition);
blockStack.Push(ifBlock);
}
else if (opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek)
{
// JMPFP and JMPTP are only used to implement short-circuiting
var ifBlock = SimplifyExpression(jumpCondition);
stack.Push(ifBlock);
}
break;
case OpCode.Jump:
var jumpOffset = (uint)instruction.Operands.First().Value;
// TODO
blockStack.Peek().Statements.Add(GotoStatement(SyntaxKind.GotoStatement, IdentifierName($"UIB_{jumpOffset:X4}")));
if (jumpOffset > instruction.Offset)
{
}
else
{
throw new NotImplementedException("Loops, ternaries, and null coalescing are not supported at this time");
}
break;
case OpCode.ReturnValue: