Files
wiicompiled/translator/tests/Translator.Tests/ModFunctionDiscoveryTests.cs
patchzyy ec226e8348 init
2026-08-23 17:10:50 +02:00

667 lines
26 KiB
C#

using System.Buffers.Binary;
using Translator.Core.Mods;
using Translator.Core.Parsing.Kamek;
using Xunit;
namespace Translator.Tests;
public sealed class ModFunctionDiscoveryTests
{
[Fact]
public void DiscoversModuleDataPointersAndPrologues()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x80];
WriteU32(code, 0x20, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x24, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x28, 0x90010014u); // stw r0,0x14(r1)
WriteU32(code, 0x2C, 0x4E800020u); // blr
WriteU32(code, 0x40, moduleBase + 0x50);
WriteU32(code, 0x50, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(KamekChunk.HeaderSize + code.Length),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module prologue scan", starts[moduleBase + 0x20]);
Assert.Equal("module data pointer", starts[moduleBase + 0x50]);
}
[Fact]
public void DiscoversModuleBaseBctrThunk()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x40];
WriteU32(code, 0x00, 0x7C6C1B78u); // mr r12,r3
WriteU32(code, 0x04, 0x7D8903A6u); // mtctr r12
WriteU32(code, 0x08, 0x4E800420u); // bctr
WriteU32(code, 0x24, 0x9421FFF0u); // stwu r1,-0x10(r1)
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module base", starts[moduleBase]);
}
[Fact]
public void DiscoversSynthesizedLeafFunctionPointers()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x9800];
WriteU32(code, 0x40, 0x3C608121u); // lis r3,0x8121
WriteU32(code, 0x44, 0x38639710u); // addi r3,r3,-0x68f0 => 0x81209710
WriteU32(code, 0x48, 0x907EAD54u); // stw r3,-0x52ac(r30)
WriteU32(code, 0x9710, 0x3C808038u); // lis r4,0x8038
WriteU32(code, 0x9714, 0x38600016u); // li r3,0x16
WriteU32(code, 0x9718, 0x80846E3Cu); // lwz r4,0x6e3c(r4)
WriteU32(code, 0x971C, 0x38000010u); // li r0,0x10
WriteU32(code, 0x9720, 0xB0640020u); // sth r3,0x20(r4)
WriteU32(code, 0x9724, 0xB004001Cu); // sth r0,0x1c(r4)
WriteU32(code, 0x9728, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[0x81209710]);
}
[Fact]
public void DiscoversSynthesizedCallbackStoredAfterRegisterSaveSetup()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x300];
// Placed past the declared code size so only the synthesized-pointer
// scan, and not the prologue scan, can attribute this start.
WriteU32(code, 0x80, 0x9421FFF0u); // callback: stwu r1,-0x10(r1)
WriteU32(code, 0x84, 0x4E800020u); // blr
WriteU32(code, 0x40, 0x9421FFF0u); // outer function
WriteU32(code, 0x44, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x100, 0x3C608170u); // lis r3,0x8170
WriteU32(code, 0x104, 0x3CA0817Bu); // unrelated setup
WriteU32(code, 0x108, 0x90010024u); // save r0
WriteU32(code, 0x10C, 0x38630080u); // addi r3,r3,0x80
WriteU32(code, 0x110, 0x93E1001Cu); // save r31
WriteU32(code, 0x114, 0x3FE08177u); // more setup
WriteU32(code, 0x118, 0x38DFEC94u);
WriteU32(code, 0x11C, 0x93C10018u);
WriteU32(code, 0x120, 0x3FC08170u);
WriteU32(code, 0x124, 0x93A10014u);
WriteU32(code, 0x128, 0x3FA08174u);
WriteU32(code, 0x12C, 0x389DCCB4u);
WriteU32(code, 0x130, 0x93810010u);
WriteU32(code, 0x134, 0x3F80817Bu);
WriteU32(code, 0x138, 0x907C0180u); // stw r3,0x180(r28)
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x80,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[moduleBase + 0x80]);
}
[Fact]
public void DiscoversSynthesizedLongLeafFunctionPointers()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x40000];
WriteU32(code, 0xF574, 0x3C608124u); // lis r3,0x8124
WriteU32(code, 0xF578, 0x38800003u); // li r4,3
WriteU32(code, 0xF57C, 0x90010014u); // stw r0,0x14(r1)
WriteU32(code, 0xF580, 0x3863F1C0u); // addi r3,r3,-0xe40 => 0x8123F1C0
WriteU32(code, 0xF584, 0x907E1D50u); // stw r3,0x1d50(r30)
WriteU32(code, 0x3F1C0, 0x3D008129u); // lis r8,0x8129
for (var offset = 0x3F1C4; offset < 0x3F204; offset += 4)
{
WriteU32(code, offset, 0x60000000u); // nop-like body
}
WriteU32(code, 0x3F204, 0x4E800020u); // blr, past the old 64-byte leaf scan window
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[0x8123F1C0]);
}
[Fact]
public void DiscoversSynthesizedPointersWrittenToDifferentRegister()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x40000];
WriteU32(code, 0x200, 0x3FE08124u); // lis r31,0x8124
WriteU32(code, 0x204, 0x389FE6B4u); // addi r4,r31,-0x194c => 0x8123E6B4, not directly stored
WriteU32(code, 0x208, 0x38A50000u); // addi r5,r5,0
WriteU32(code, 0x20C, 0x381FE6B4u); // addi r0,r31,-0x194c => 0x8123E6B4
WriteU32(code, 0x210, 0x901E003Cu); // stw r0,0x3c(r30)
WriteU32(code, 0x3E6B4, 0x3C60801Cu); // lis r3,0x801c
WriteU32(code, 0x3E6B8, 0x8063AB20u); // lwz r3,-0x54e0(r3)
WriteU32(code, 0x3E6BC, 0x3C03B180u); // addis r0,r3,-0x4e80
WriteU32(code, 0x3E6C0, 0x28000020u); // cmplwi r0,0x20
WriteU32(code, 0x3E6C4, 0x4D820020u); // beqlr
WriteU32(code, 0x3E6C8, 0x3C608126u); // lis r3,0x8126
WriteU32(code, 0x3E6CC, 0x38636650u); // addi r3,r3,0x6650
WriteU32(code, 0x3E6D0, 0x4BFCAF08u); // b some helper
WriteU32(code, 0x3E6D4, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[0x8123E6B4]);
}
[Fact]
public void DiscoversSynthesizedComparatorPassedThroughExternalTailCall()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x20000];
// Mirrors the Retro Rewind trampoline at 0x8171FCB4: a lone backward
// branch referenced only as a callback argument to a base-game routine.
WriteU32(code, 0x1FBC8, 0x9421FFF0u); // helper: stwu r1,-0x10(r1)
WriteU32(code, 0x1FBCC, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x1FBD0, 0x4E800020u); // blr
WriteU32(code, 0x1FCB4, EncodeBranch(moduleBase + 0x1FCB4, moduleBase + 0x1FBC8)); // trampoline
WriteU32(code, 0x1FCCC, 0x3CC08172u); // lis r6,0x8172
WriteU32(code, 0x1FCD0, 0x38A00018u); // li r5,0x18
WriteU32(code, 0x1FCD4, 0x38C6FCB4u); // addi r6,r6,-0x34c => 0x8171FCB4
WriteU32(code, 0x1FCD8, EncodeBranch(moduleBase + 0x1FCD8, 0x80011B00u)); // tail call into base sort routine
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[moduleBase + 0x1FCB4]);
}
[Fact]
public void DiscoversSynthesizedCallbackPassedAsCallArgument()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x20000];
WriteU32(code, 0x1000, 0x38600001u); // prologue-less leaf callback: li r3,1
WriteU32(code, 0x1004, 0x4E800020u); // blr
WriteU32(code, 0x2000, 0x3C808170u); // lis r4,0x8170
WriteU32(code, 0x2004, 0x38841000u); // addi r4,r4,0x1000 => callback
WriteU32(code, 0x2008, 0x48000001u | (0x3000u - 0x2008u)); // bl moduleBase+0x3000
WriteU32(code, 0x3000, 0x4E800020u); // callee: blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module synthesized pointer", starts[moduleBase + 0x1000]);
}
[Fact]
public void IgnoresAsciiDataThatDecodesAsForwardBranch()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x20000];
// "HeyhoShipGBA.brres" - 'H' (0x48) decodes as an unconditional forward
// branch whose target lands far outside executable MEM1.
var name = "HeyhoShipGBA.brres\0\0"u8.ToArray();
name.CopyTo(code, 0x1B5FC);
WriteU32(code, 0x2000, 0x3C808172u); // lis r4,0x8172
WriteU32(code, 0x2004, 0x3884B5FCu); // addi r4,r4,-0x4a04 => 0x8171B5FC string address
WriteU32(code, 0x2008, 0x48000001u | (0x3000u - 0x2008u)); // bl: string passed as argument
WriteU32(code, 0x3000, 0x4E800020u); // callee: blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.False(starts.ContainsKey(moduleBase + 0x1B5FC));
}
[Fact]
public void IgnoresSynthesizedAddressWhoseRegisterDiesAtCall()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x20000];
WriteU32(code, 0x1000, 0x38600001u); // leaf-looking target: li r3,1
WriteU32(code, 0x1004, 0x4E800020u); // blr
WriteU32(code, 0x2000, 0x3D808170u); // lis r12,0x8170
WriteU32(code, 0x2004, 0x398C1000u); // addi r12,r12,0x1000
WriteU32(code, 0x2008, 0x48000001u | (0x3000u - 0x2008u)); // bl clobbers r12 before any use
WriteU32(code, 0x3000, 0x4E800020u); // callee: blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.DoesNotContain(starts, kvp => kvp.Value == "module synthesized pointer");
}
[Fact]
public void DiscoversModuleDataPointerToLeafFunction()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x200];
WriteU32(code, 0x20, moduleBase + 0x100);
WriteU32(code, 0x100, 0x806300DCu); // lwz r3,0xdc(r3)
WriteU32(code, 0x104, 0x7C802378u); // mr r0,r4
WriteU32(code, 0x108, 0x2C030000u); // cmpwi r3,0
WriteU32(code, 0x10C, 0x38600000u); // li r3,0
WriteU32(code, 0x110, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x100]);
}
[Fact]
public void DiscoversAppendedModuleDataPointerToLeafFunction()
{
const uint moduleBase = 0x81200000u;
var relocatedImage = new byte[0x300];
WriteU32(relocatedImage, 0x20, moduleBase + 0x200);
WriteU32(relocatedImage, 0x200, 0x38600001u); // li r3,1
WriteU32(relocatedImage, 0x204, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x100,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(0x100 + KamekChunk.HeaderSize),
codeBlob: relocatedImage[..0x100],
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, relocatedImage)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x200]);
}
[Fact]
public void DiscoversShortTailEntryInsideMappedPayloadFunction()
{
const uint moduleBase = 0x81700000u;
var relocatedImage = new byte[0x300];
WriteU32(relocatedImage, 0x150, 0x9421FFF0u); // stwu r1,-0x10(r1), executable tail target
WriteU32(relocatedImage, 0x154, 0x4E800020u); // blr
WriteU32(relocatedImage, 0x200, 0x9421FFF0u); // mapped public payload function
WriteU32(relocatedImage, 0x204, 0x4E800020u); // blr
WriteU32(relocatedImage, 0x230, EncodeBranch(moduleBase + 0x230, moduleBase + 0x200)); // previous helper tail
WriteU32(relocatedImage, 0x234, 0x38800001u); // li r4,1
WriteU32(relocatedImage, 0x238, EncodeBranch(moduleBase + 0x238, moduleBase + 0x150)); // b target
WriteU32(relocatedImage, 0x23C, 0x000058B4u); // inline data between tail entry and next prologue
WriteU32(relocatedImage, 0x240, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(relocatedImage, 0x244, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x100,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(0x100 + KamekChunk.HeaderSize),
codeBlob: relocatedImage[..0x100],
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, relocatedImage)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module tail-entry scan", starts[moduleBase + 0x234]);
Assert.False(starts.ContainsKey(moduleBase + 0x23C));
}
[Fact]
public void KeepsPrologueScanScopedToOriginalCodeSize()
{
const uint moduleBase = 0x81200000u;
var relocatedImage = new byte[0x300];
WriteU32(relocatedImage, 0x200, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(relocatedImage, 0x204, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x100,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(0x100 + KamekChunk.HeaderSize),
codeBlob: relocatedImage[..0x100],
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, relocatedImage);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x200);
}
[Fact]
public void DiscoversLeafFunctionBetweenReturnedFunctionAndNextPrologue()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x500];
WriteU32(code, 0x320, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x324, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x328, 0x90010014u); // stw r0,0x14(r1)
WriteU32(code, 0x32C, 0x80010014u); // lwz r0,0x14(r1)
WriteU32(code, 0x330, 0x7C0803A6u); // mtlr r0
WriteU32(code, 0x334, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x338, 0x4E800020u); // blr
WriteU32(code, 0x33C, 0x3C60809Cu); // lis r3,0x809c
WriteU32(code, 0x340, 0x3CA08129u); // lis r5,0x8129
WriteU32(code, 0x344, 0x80831E38u); // lwz r4,0x1e38(r3)
WriteU32(code, 0x348, 0x8065FD1Cu); // lwz r3,-0x2e4(r5)
WriteU32(code, 0x34C, 0x2C040048u); // cmpwi r4,0x48
WriteU32(code, 0x350, 0x4D820020u); // beqlr
WriteU32(code, 0x354, 0x38000000u); // li r0,0
WriteU32(code, 0x358, 0x90030060u); // stw r0,0x60(r3)
WriteU32(code, 0x35C, 0x4E800020u); // blr
WriteU32(code, 0x3A0, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x3A4, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0x33C]);
}
[Fact]
public void DiscoversTailThunkBetweenBranchedFunctionAndNextPrologue()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x500];
WriteU32(code, 0x240, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x244, 0x4E800020u); // blr
WriteU32(code, 0x320, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x324, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x328, 0x90010014u); // stw r0,0x14(r1)
WriteU32(code, 0x32C, 0x38630020u); // addi r3,r3,0x20
WriteU32(code, 0x330, 0x48000010u); // b moduleBase+0x340
WriteU32(code, 0x334, 0x3C608129u); // lis r3,0x8129
WriteU32(code, 0x338, 0x38631C5Cu); // addi r3,r3,0x1c5c
WriteU32(code, 0x33C, 0x4BFFFF04u); // b moduleBase+0x240
WriteU32(code, 0x340, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x344, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0x334]);
}
[Fact]
public void DoesNotSplitCountedLoopContinuationAfterConditionalReturn()
{
const uint moduleBase = 0x81750000u;
var code = new byte[0x200];
WriteU32(code, 0x100, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x120, 0x80050008u); // lwz r0,8(r5)
WriteU32(code, 0x124, 0x7C002000u); // cmpw r0,r4
WriteU32(code, 0x128, 0x40820008u); // bne moduleBase+0x130
WriteU32(code, 0x12C, 0x4E800020u); // blr
WriteU32(code, 0x130, 0x38A50004u); // addi r5,r5,4
WriteU32(code, 0x134, 0x4200FFECu); // bdnz moduleBase+0x120
WriteU32(code, 0x138, 0x4E800020u); // blr
WriteU32(code, 0x180, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x184, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.False(starts.ContainsKey(moduleBase + 0x130));
}
[Fact]
public void DiscoversLeafFunctionThatTailBranchesToBaseCode()
{
const uint moduleBase = 0x8174FBA0u;
var code = new byte[0x500];
WriteU32(code, 0x300, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x304, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x308, 0x4E800020u); // blr
WriteU32(code, 0x30C, 0x38000008u); // li r0,8
WriteU32(code, 0x310, 0x3880FFFFu); // li r4,-1
WriteU32(code, 0x314, 0x90832AB4u); // stw r4,0x2ab4(r3)
WriteU32(code, 0x318, 0x38A00000u); // li r5,0
WriteU32(code, 0x31C, 0x80C3072Cu); // lwz r6,0x72c(r3)
WriteU32(code, 0x320, 0x7C0903A6u); // mtctr r0
WriteU32(code, 0x324, 0x80860000u); // lwz r4,0(r6)
WriteU32(code, 0x328, 0x38C60004u); // addi r6,r6,4
WriteU32(code, 0x32C, 0x98A401E9u); // stb r5,0x1e9(r4)
WriteU32(code, 0x330, 0x4200FFF4u); // bdnz -0xc
WriteU32(code, 0x334, 0x38000000u); // li r0,0
WriteU32(code, 0x338, EncodeBranch(moduleBase + 0x338, 0x80841010u)); // b base code
WriteU32(code, 0x33C, 0x9421FFC0u); // stwu r1,-0x40(r1)
WriteU32(code, 0x340, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x344, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0x30C]);
}
[Fact]
public void DiscoversLeafFunctionAfterExternalTailBranch()
{
const uint moduleBase = 0x81700000u;
var code = new byte[0x44000];
WriteU32(code, 0x42FF8, 0x806302BCu); // lwz r3,0x2bc(r3)
WriteU32(code, 0x42FFC, 0x38843000u); // addi r4,r4,0x3000
WriteU32(code, 0x43000, EncodeBranch(moduleBase + 0x43000, 0x807E9A38u)); // b base code
WriteU32(code, 0x43004, 0x80630048u); // lwz r3,0x48(r3)
WriteU32(code, 0x43008, 0x38800000u); // li r4,0
WriteU32(code, 0x4300C, 0x80630000u); // lwz r3,0(r3)
WriteU32(code, 0x43010, EncodeBranch(moduleBase + 0x43010, 0x805BDF44u)); // b base code
WriteU32(code, 0x43014, 0x9421FFE0u); // stwu r1,-0x20(r1)
WriteU32(code, 0x43018, 0x7C0802A6u); // mflr r0
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0x43004]);
}
private static void WriteU32(byte[] data, int offset, uint value) =>
BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(offset, 4), value);
private static uint EncodeBranch(uint source, uint target)
{
var delta = unchecked((int)(target - source));
return 0x48000000u | ((uint)delta & 0x03FFFFFCu);
}
}