mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-21 13:54:55 -04:00
fix 279 (#297)
* fix 279 * treat conditional calls as calls * keep local linked branches as jumps
This commit is contained in:
@@ -230,6 +230,11 @@ public sealed class PpcDisassembler : IDisposable
|
||||
{
|
||||
foreach (var target in ins.BranchTargets)
|
||||
{
|
||||
// A conditional call to another function returns to its fallthrough.
|
||||
if (ins.IsCall && IsKnownExternalFunctionEntry(target))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
localControlFlowTargets.Add(target);
|
||||
if (!reachable.Contains(target))
|
||||
{
|
||||
|
||||
@@ -690,10 +690,11 @@ public sealed partial class PpcLifter
|
||||
var rawInstr = ReadRawInstruction(ins);
|
||||
var bo = (int)((rawInstr >> 21) & 0x1F);
|
||||
var bi = (int)((rawInstr >> 16) & 0x1F);
|
||||
var fallthrough = $"0x{ins.EndAddress:X8}";
|
||||
var targetLabel = mnemonic == "bcctr"
|
||||
? $"indirect_ctr_{ins.Address:X8}"
|
||||
: $"call_ctr_{ins.Address:X8}_{ins.EndAddress:X8}";
|
||||
var linked = mnemonic == "bcctrl";
|
||||
var targetLabel = linked
|
||||
? $"call_ctr_{ins.Address:X8}_{ins.EndAddress:X8}"
|
||||
: $"indirect_ctr_{ins.Address:X8}";
|
||||
var fallthrough = linked ? LinkedFallthroughLabel(ins) : $"0x{ins.EndAddress:X8}";
|
||||
|
||||
return new[] { new IrBranch("raw", targetLabel, fallthrough, BuildBoConditionExpression(bo, bi, allowCtr: false)) };
|
||||
}
|
||||
@@ -1943,6 +1944,15 @@ public sealed partial class PpcLifter
|
||||
};
|
||||
|
||||
case "bltl":
|
||||
case "bgtl":
|
||||
case "beql":
|
||||
case "bsol":
|
||||
case "bgel":
|
||||
case "blel":
|
||||
case "bnel":
|
||||
case "bnsl":
|
||||
case "bdnzl":
|
||||
case "bdzl":
|
||||
case "bcl":
|
||||
{
|
||||
var rawInstr = ReadRawInstruction(ins);
|
||||
@@ -1978,14 +1988,13 @@ public sealed partial class PpcLifter
|
||||
var rawInstr = ReadRawInstruction(ins);
|
||||
var bo = (int)((rawInstr >> 21) & 0x1F);
|
||||
var bi = (int)((rawInstr >> 16) & 0x1F);
|
||||
var fallthrough = $"0x{ins.EndAddress:X8}";
|
||||
var targetLabel = $"call_lr_{ins.Address:X8}_{ins.EndAddress:X8}";
|
||||
var instructions = new List<IrInstruction>();
|
||||
if ((bo & 0x04) == 0)
|
||||
{
|
||||
instructions.Add(new IrBinary("ctr", IrValue.Register("ctr"), IrValue.Imm(-1), "add"));
|
||||
}
|
||||
instructions.Add(new IrBranch("raw", targetLabel, fallthrough, BuildBoConditionExpression(bo, bi, allowCtr: true)));
|
||||
instructions.Add(new IrBranch("raw", targetLabel, LinkedFallthroughLabel(ins), BuildBoConditionExpression(bo, bi, allowCtr: true)));
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@@ -2004,10 +2013,9 @@ public sealed partial class PpcLifter
|
||||
crField = NormalizeRegister(crOp.Name);
|
||||
}
|
||||
|
||||
var fallthrough = $"0x{ins.EndAddress:X8}";
|
||||
var targetLabel = $"call_lr_{ins.Address:X8}_{ins.EndAddress:X8}";
|
||||
var cond = mnemonic.Replace("lrl", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
return new[] { new IrBranch(cond, targetLabel, fallthrough, crField) };
|
||||
return new[] { new IrBranch(cond, targetLabel, LinkedFallthroughLabel(ins), crField) };
|
||||
}
|
||||
|
||||
case "bne":
|
||||
@@ -3033,6 +3041,10 @@ private static string TargetLabel(PpcInstruction ins, HashSet<uint> validAddress
|
||||
return $"0x{target:X8}";
|
||||
}
|
||||
|
||||
// Not-taken path of a linked conditional branch: LR still receives the return address.
|
||||
private static string LinkedFallthroughLabel(PpcInstruction ins) =>
|
||||
$"link_branch_{ins.EndAddress:X8}_{ins.EndAddress:X8}";
|
||||
|
||||
private static string BuildBoConditionExpression(int bo, int bi, bool allowCtr)
|
||||
{
|
||||
var crFieldIdx = bi / 4;
|
||||
|
||||
@@ -420,7 +420,7 @@ public sealed class FunctionTranslator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryBuildSyntheticBlock(label, out var synth))
|
||||
if (TryBuildSyntheticBlock(label, existingLabels, out var synth))
|
||||
{
|
||||
irBlocks.Add(synth);
|
||||
existingLabels.Add(label);
|
||||
@@ -839,7 +839,7 @@ public sealed class FunctionTranslator
|
||||
return rewritten;
|
||||
}
|
||||
|
||||
private static bool TryBuildSyntheticBlock(string label, out IrBasicBlock block)
|
||||
private static bool TryBuildSyntheticBlock(string label, IReadOnlySet<string> localLabels, out IrBasicBlock block)
|
||||
{
|
||||
const string IndirectCtrPrefix = "indirect_ctr_";
|
||||
const string CallCtrPrefix = "call_ctr_";
|
||||
@@ -883,11 +883,18 @@ public sealed class FunctionTranslator
|
||||
|
||||
if (TryParseLinkBranchLabel(label, LinkBranchPrefix, out fallthrough, out var target))
|
||||
{
|
||||
block = new IrBasicBlock(label, new IrInstruction[]
|
||||
{
|
||||
new IrAssign("lr", IrValue.Imm((int)fallthrough)),
|
||||
new IrJump($"0x{target:X8}")
|
||||
});
|
||||
block = new IrBasicBlock(label, target == fallthrough || localLabels.Contains($"0x{target:X8}")
|
||||
? new IrInstruction[]
|
||||
{
|
||||
new IrAssign("lr", IrValue.Imm((int)fallthrough)),
|
||||
new IrJump($"0x{target:X8}")
|
||||
}
|
||||
: new IrInstruction[]
|
||||
{
|
||||
new IrAssign("lr", IrValue.Imm((int)fallthrough)),
|
||||
new IrCall(string.Empty, $"0x{target:X8}", BuildAbiCallArgs()),
|
||||
new IrJump($"0x{fallthrough:X8}")
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,37 @@ public class FunctionTranslatorCoverageTests
|
||||
Assert.Equal(TimeSpan.Zero, discovery.Metrics.CodeGen);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x4D820021u)] // beqlrl
|
||||
[InlineData(0x4D820421u)] // bcctrl, BO=12 BI=2
|
||||
public void Discover_LinkedConditionalRegisterBranchUpdatesLrWhenNotTaken(uint branchWord)
|
||||
{
|
||||
var memory = new byte[MemoryLayout.RamSize];
|
||||
WriteWord(memory, 0x80001000, branchWord);
|
||||
WriteWord(memory, 0x80001004, 0x7C6802A6); // mflr r3
|
||||
WriteWord(memory, 0x80001008, 0x4E800020); // blr
|
||||
|
||||
var image = new ProgramImage(
|
||||
memory,
|
||||
AddressRange.FromStartAndSize(0x80001000, 0x1004),
|
||||
AddressRange.FromStartAndSize(0x80001000, 0x1004),
|
||||
default,
|
||||
"test");
|
||||
var discovery = new FunctionTranslator(image).Discover(
|
||||
0x80001000,
|
||||
TranslationOptions.Default with { MaxBytes = 0x20, AllowUnsupportedInstructions = true });
|
||||
|
||||
var blocks = discovery.Ssa.Function.Blocks;
|
||||
var branch = Assert.Single(blocks.SelectMany(b => b.Instructions).OfType<IrBranch>());
|
||||
Assert.Equal("link_branch_80001004_80001004", branch.FalseLabel);
|
||||
|
||||
var notTaken = Assert.Single(blocks, b => b.Label == branch.FalseLabel);
|
||||
var lrAssign = Assert.Single(notTaken.Instructions.OfType<IrAssign>());
|
||||
Assert.StartsWith("lr", lrAssign.Destination, StringComparison.Ordinal);
|
||||
Assert.Equal(unchecked((int)0x80001004u), lrAssign.Value.Constant);
|
||||
Assert.Equal("0x80001004", Assert.IsType<IrJump>(notTaken.Instructions[^1]).TargetLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Translate_PreservesFallthroughAfterDirectLinkBranch()
|
||||
{
|
||||
@@ -354,18 +385,20 @@ public class FunctionTranslatorCoverageTests
|
||||
Assert.Equal("f13", abiArgs[^1].RegisterName);
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> NoLocalLabels = new();
|
||||
|
||||
[Fact]
|
||||
public void TryBuildSyntheticBlock_CoversIndirectAndCallForms()
|
||||
{
|
||||
var indirectArgs = new object?[] { "indirect_ctr_80001234", null! };
|
||||
var indirectArgs = new object?[] { "indirect_ctr_80001234", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", indirectArgs));
|
||||
var indirect = Assert.IsType<IrBasicBlock>(indirectArgs[1]);
|
||||
var indirect = Assert.IsType<IrBasicBlock>(indirectArgs[2]);
|
||||
Assert.Equal("indirect_ctr_80001234", indirect.Label);
|
||||
Assert.Equal("ctr", Assert.IsType<IrIndirectJump>(Assert.Single(indirect.Instructions)).Target.RegisterName);
|
||||
|
||||
var callCtrArgs = new object?[] { "call_ctr_80001000_80001004", null! };
|
||||
var callCtrArgs = new object?[] { "call_ctr_80001000_80001004", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", callCtrArgs));
|
||||
var callCtr = Assert.IsType<IrBasicBlock>(callCtrArgs[1]);
|
||||
var callCtr = Assert.IsType<IrBasicBlock>(callCtrArgs[2]);
|
||||
Assert.Collection(
|
||||
callCtr.Instructions,
|
||||
ins =>
|
||||
@@ -382,9 +415,9 @@ public class FunctionTranslatorCoverageTests
|
||||
},
|
||||
ins => Assert.Equal("0x80001004", Assert.IsType<IrJump>(ins).TargetLabel));
|
||||
|
||||
var callLrArgs = new object?[] { "call_lr_80002000_80002004", null! };
|
||||
var callLrArgs = new object?[] { "call_lr_80002000_80002004", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", callLrArgs));
|
||||
var callLr = Assert.IsType<IrBasicBlock>(callLrArgs[1]);
|
||||
var callLr = Assert.IsType<IrBasicBlock>(callLrArgs[2]);
|
||||
Assert.Collection(
|
||||
callLr.Instructions,
|
||||
ins =>
|
||||
@@ -407,9 +440,39 @@ public class FunctionTranslatorCoverageTests
|
||||
},
|
||||
ins => Assert.Equal("0x80002004", Assert.IsType<IrJump>(ins).TargetLabel));
|
||||
|
||||
var absoluteArgs = new object?[] { "0x800D7FCC", null! };
|
||||
var notTakenArgs = new object?[] { "link_branch_80002004_80002004", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", notTakenArgs));
|
||||
var notTaken = Assert.IsType<IrBasicBlock>(notTakenArgs[2]);
|
||||
Assert.Collection(
|
||||
notTaken.Instructions,
|
||||
ins =>
|
||||
{
|
||||
var assign = Assert.IsType<IrAssign>(ins);
|
||||
Assert.Equal("lr", assign.Destination);
|
||||
Assert.Equal(unchecked((int)0x80002004u), assign.Value.Constant);
|
||||
},
|
||||
ins => Assert.Equal("0x80002004", Assert.IsType<IrJump>(ins).TargetLabel));
|
||||
|
||||
var linkedCallArgs = new object?[] { "link_branch_80002004_800D7FCC", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", linkedCallArgs));
|
||||
var linkedCall = Assert.IsType<IrBasicBlock>(linkedCallArgs[2]);
|
||||
Assert.Collection(
|
||||
linkedCall.Instructions,
|
||||
ins => Assert.Equal(unchecked((int)0x80002004u), Assert.IsType<IrAssign>(ins).Value.Constant),
|
||||
ins => Assert.Equal("0x800D7FCC", Assert.IsType<IrCall>(ins).Target),
|
||||
ins => Assert.Equal("0x80002004", Assert.IsType<IrJump>(ins).TargetLabel));
|
||||
|
||||
var localLinkedArgs = new object?[] { "link_branch_80002004_80002000", new HashSet<string> { "0x80002000" }, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", localLinkedArgs));
|
||||
var localLinked = Assert.IsType<IrBasicBlock>(localLinkedArgs[2]);
|
||||
Assert.Collection(
|
||||
localLinked.Instructions,
|
||||
ins => Assert.Equal(unchecked((int)0x80002004u), Assert.IsType<IrAssign>(ins).Value.Constant),
|
||||
ins => Assert.Equal("0x80002000", Assert.IsType<IrJump>(ins).TargetLabel));
|
||||
|
||||
var absoluteArgs = new object?[] { "0x800D7FCC", NoLocalLabels, null! };
|
||||
Assert.True(InvokePrivate<bool>("TryBuildSyntheticBlock", absoluteArgs));
|
||||
var absolute = Assert.IsType<IrBasicBlock>(absoluteArgs[1]);
|
||||
var absolute = Assert.IsType<IrBasicBlock>(absoluteArgs[2]);
|
||||
Assert.Equal("0x800D7FCC", absolute.Label);
|
||||
Assert.Collection(
|
||||
absolute.Instructions,
|
||||
@@ -421,9 +484,9 @@ public class FunctionTranslatorCoverageTests
|
||||
},
|
||||
ins => Assert.Null(Assert.IsType<IrReturn>(ins).Value));
|
||||
|
||||
var invalidArgs = new object?[] { "plain_label", null! };
|
||||
var invalidArgs = new object?[] { "plain_label", NoLocalLabels, null! };
|
||||
Assert.False(InvokePrivate<bool>("TryBuildSyntheticBlock", invalidArgs));
|
||||
Assert.Null(invalidArgs[1]);
|
||||
Assert.Null(invalidArgs[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Translator.Core.Disassembly;
|
||||
using Translator.Core.Ir;
|
||||
@@ -37,6 +38,85 @@ public class PpcLifterAdditionalCoverageTests
|
||||
Assert.Equal($"0x{branch.EndAddress:X8}", decision.FalseLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalCallTargetIsNotDecodedAsLocalCode()
|
||||
{
|
||||
const uint entry = 0x80000000;
|
||||
const uint callee = entry + 0x10;
|
||||
|
||||
var image = TranslatorCppTestHarness.CreateImage(
|
||||
(entry + 0x00, 0x41820011), // beql callee
|
||||
(entry + 0x04, 0x38600001), // li r3, 1
|
||||
(entry + 0x08, 0x4E800020), // blr
|
||||
(callee + 0x00, 0x38600002), // li r3, 2
|
||||
(callee + 0x04, 0x4E800020)); // blr
|
||||
|
||||
using var disassembler = new PpcDisassembler();
|
||||
var instructions = disassembler.DisassembleFunction(
|
||||
image, entry, maxInstructions: 32, maxBytes: 0x80, knownFunctionEntryPoints: new HashSet<uint> { entry, callee });
|
||||
|
||||
Assert.Equal(new[] { entry, entry + 4, entry + 8 }, instructions.Select(ins => ins.Address));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalCallToLocalTargetKeepsLocalEdge()
|
||||
{
|
||||
const uint entry = 0x80000000;
|
||||
|
||||
var image = TranslatorCppTestHarness.CreateImage(
|
||||
(entry + 0x00, 0x4182000D), // beql local
|
||||
(entry + 0x04, 0x38600001), // li r3, 1
|
||||
(entry + 0x08, 0x4E800020), // blr
|
||||
(entry + 0x0C, 0x38600002), // local: li r3, 2
|
||||
(entry + 0x10, 0x4E800020)); // blr
|
||||
|
||||
using var disassembler = new PpcDisassembler();
|
||||
var instructions = disassembler.DisassembleFunction(
|
||||
image, entry, maxInstructions: 32, maxBytes: 0x80, knownFunctionEntryPoints: new HashSet<uint> { entry });
|
||||
|
||||
Assert.Contains(instructions, ins => ins.Address == entry + 0x0C);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x41820009u, "beql", "0x80001008")]
|
||||
[InlineData(0x41810009u, "bgtl", "0x80001008")]
|
||||
[InlineData(0x40820009u, "bnel", "0x80001008")]
|
||||
[InlineData(0x42000009u, "bdnzl", "0x80001008")]
|
||||
[InlineData(0x42400009u, "bdzl", "0x80001008")]
|
||||
public void LinkedConditionalRelativeBranchFormsSetLrOnBothPaths(uint word, string mnemonic, string target)
|
||||
{
|
||||
var branch = PpcDecoder.Decode(0x80001000, word);
|
||||
var ir = Assert.Single(new PpcLifter().Lift(new[] { branch })).Ir;
|
||||
|
||||
Assert.Equal(mnemonic, branch.Mnemonic);
|
||||
|
||||
var lrAssign = Assert.Single(ir.OfType<IrAssign>());
|
||||
Assert.Equal("lr", lrAssign.Destination);
|
||||
Assert.Equal(unchecked((int)0x80001004u), lrAssign.Value.Constant);
|
||||
Assert.IsType<IrAssign>(ir[^2]);
|
||||
|
||||
var decision = Assert.IsType<IrBranch>(ir[^1]);
|
||||
Assert.Equal("raw", decision.Condition);
|
||||
Assert.Equal($"link_branch_80001004_{target[2..]}", decision.TrueLabel);
|
||||
Assert.Equal("0x80001004", decision.FalseLabel);
|
||||
Assert.Equal(mnemonic.StartsWith("bd", StringComparison.Ordinal), ir.OfType<IrBinary>().Any(b => b.Destination == "ctr"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x4D820021u)]
|
||||
[InlineData(0x4D820421u)]
|
||||
[InlineData(0x4D030021u)]
|
||||
public void LinkedConditionalRegisterBranchesRouteNotTakenPathThroughLrUpdate(uint word)
|
||||
{
|
||||
var branch = PpcDecoder.Decode(0x80001000, word);
|
||||
var ir = Assert.Single(new PpcLifter().Lift(new[] { branch })).Ir;
|
||||
|
||||
var decision = Assert.IsType<IrBranch>(ir[^1]);
|
||||
Assert.Equal("link_branch_80001004_80001004", decision.FalseLabel);
|
||||
Assert.StartsWith(branch.Mnemonic.Contains("ctr", StringComparison.Ordinal) ? "call_ctr_" : "call_lr_", decision.TrueLabel, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(ir, ins => ins is IrAssign { Destination: "lr" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LiftsAdditionalNonDotArithmeticAndLogicalForms()
|
||||
{
|
||||
@@ -365,6 +445,7 @@ public class PpcLifterAdditionalCoverageTests
|
||||
var bclrl = Instruction((12u << 21) | (8u << 16), "bclrl", new PpcConditionRegisterOperand("cr2", 8));
|
||||
var bclrlBranch = Assert.IsType<IrBranch>(Assert.Single(Assert.Single(lifter.Lift(new[] { bclrl })).Ir));
|
||||
Assert.Contains("call_lr_80000000_80000004", bclrlBranch.TrueLabel, StringComparison.Ordinal);
|
||||
Assert.Equal("link_branch_80000004_80000004", bclrlBranch.FalseLabel);
|
||||
Assert.Equal("raw", bclrlBranch.Condition);
|
||||
Assert.Contains("GetCRBit(ctx, 2, 0)", bclrlBranch.ConditionRegister, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
@@ -564,6 +564,7 @@ public class PpcLifterCoverageTests
|
||||
Assert.Equal("beq", beqlrlBranch.Condition);
|
||||
Assert.Equal("cr3", beqlrlBranch.ConditionRegister);
|
||||
Assert.Contains("call_lr_80000000_80000004", beqlrlBranch.TrueLabel, StringComparison.Ordinal);
|
||||
Assert.Equal("link_branch_80000004_80000004", beqlrlBranch.FalseLabel);
|
||||
|
||||
var bgtlrl = Instruction(0, "bgtlrl");
|
||||
var bgtlrlBranch = Assert.IsType<IrBranch>(Assert.Single(Assert.Single(lifter.Lift(new[] { bgtlrl })).Ir));
|
||||
|
||||
Reference in New Issue
Block a user