* fix 279

* treat conditional calls as calls

* keep local linked branches as jumps
This commit is contained in:
patchzyy
2026-10-18 14:03:38 +02:00
committed by GitHub
parent 95a49f3c89
commit ed6fea0efb
6 changed files with 194 additions and 25 deletions
@@ -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));