mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
[WIP] If-elseif-else clause decompilation
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user